summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXavier Del Campo Romero <xavi92@disroot.org>2026-02-03 15:59:29 +0100
committerXavier Del Campo Romero <xavi92@disroot.org>2026-02-03 16:26:52 +0100
commitfdb64c59865e4db76addfb8222f6421443e25240 (patch)
tree0de2bad9f758c69b7fb9ba2670653c8515a3a70d
parent207176de48d5e44c8d3e6318b526db5d772dd008 (diff)
Import files from xxcc
-rw-r--r--CMakeLists.txt28
-rw-r--r--atm_db.cpp42
-rw-r--r--atm_db.h37
-rw-r--r--client.cpp61
-rw-r--r--client.h40
-rw-r--r--credentials.cpp218
-rw-r--r--credentials.h45
-rw-r--r--direction.h6
-rw-r--r--jiddb.cpp912
-rw-r--r--jiddb.h114
-rw-r--r--libomemo-c/CMakeLists.txt23
-rw-r--r--omemo_db.cpp93
-rw-r--r--omemo_db.h42
-rw-r--r--qxmpp/CMakeLists.txt18
m---------qxmpp/qxmpp0
-rw-r--r--trust_db.cpp415
-rw-r--r--trust_db.h72
17 files changed, 2163 insertions, 3 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e12d803..be9668a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -7,7 +7,7 @@ set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
-find_package(Qt6 REQUIRED COMPONENTS Quick Sql)
+find_package(Qt6 REQUIRED COMPONENTS Core Quick Sql)
# QXmpp requires C++17.
set(CMAKE_CXX_STANDARD 17)
@@ -16,8 +16,21 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
qt_standard_project_setup(REQUIRES 6.2)
qt_add_executable(${PROJECT_NAME}
+ atm_db.cpp
+ atm_db.h
+ client.cpp
+ client.h
+ credentials.cpp
+ credentials.h
+ direction.h
+ jiddb.cpp
+ jiddb.h
main.cpp
+ omemo_db.cpp
+ omemo_db.h
test.h
+ trust_db.cpp
+ trust_db.h
)
qt_add_qml_module(${PROJECT_NAME}
@@ -32,6 +45,10 @@ qt_add_qml_module(${PROJECT_NAME}
Main.qml
)
+find_package(Qt6Keychain REQUIRED)
+target_link_libraries(${PROJECT_NAME} PRIVATE
+ Qt6Keychain::Qt6Keychain)
+
# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
# If you are developing for iOS or macOS you should consider setting an
# explicit, fixed bundle identifier manually though.
@@ -44,10 +61,15 @@ set_target_properties(${PROJECT_NAME} PROPERTIES
)
target_link_libraries(${PROJECT_NAME}
- PRIVATE Qt6::Core
- Qt6::Quick
+ PRIVATE
+ Qt6::Core
+ Qt6::Quick
+ Qt6::Sql
)
+add_subdirectory(libomemo-c)
+add_subdirectory(qxmpp)
+
include(GNUInstallDirs)
install(TARGETS ${PROJECT_NAME}
BUNDLE DESTINATION .
diff --git a/atm_db.cpp b/atm_db.cpp
new file mode 100644
index 0000000..f038959
--- /dev/null
+++ b/atm_db.cpp
@@ -0,0 +1,42 @@
+#include "atm_db.h"
+#include <QXmppFutureUtils_p.h>
+
+AtmDb::AtmDb(const QString &jid, const JidDb &db) :
+ TrustDb(jid, db)
+{}
+
+QXmppTask<void> AtmDb::addKeysForPostponedTrustDecisions(
+ const QString &encryption, const QByteArray &senderKeyId,
+ const QList<QXmppTrustMessageKeyOwner> &keyOwners)
+{
+ return QXmpp::Private::makeReadyTask();
+}
+
+QXmppTask<void> AtmDb::removeKeysForPostponedTrustDecisions(
+ const QString &encryption,
+ const QList<QByteArray> &keyIdsForAuthentication,
+ const QList<QByteArray> &keyIdsForDistrusting)
+{
+ return QXmpp::Private::makeReadyTask();
+}
+
+QXmppTask<void> AtmDb::removeKeysForPostponedTrustDecisions(
+ const QString &encryption,
+ const QList<QByteArray> &senderKeyIds)
+{
+ return QXmpp::Private::makeReadyTask();
+}
+
+QXmppTask<void> AtmDb::removeKeysForPostponedTrustDecisions(
+ const QString &encryption)
+{
+ return QXmpp::Private::makeReadyTask();
+}
+
+QXmppTask<QHash<bool, QMultiHash<QString, QByteArray>>>
+ AtmDb::keysForPostponedTrustDecisions(const QString &encryption,
+ const QList<QByteArray> &senderKeyIds)
+{
+ return QXmpp::Private::makeReadyTask(
+ QHash<bool, QMultiHash<QString, QByteArray>>());
+}
diff --git a/atm_db.h b/atm_db.h
new file mode 100644
index 0000000..0cbc247
--- /dev/null
+++ b/atm_db.h
@@ -0,0 +1,37 @@
+#ifndef ATM_DB_H
+#define ATM_DB_H
+
+#include "jiddb.h"
+#include "trust_db.h"
+#include <QXmppAtmTrustStorage.h>
+#include <QXmppTask.h>
+#include <QXmppTrustMessageKeyOwner.h>
+#include <QByteArray>
+#include <QHash>
+#include <QMultiHash>
+#include <QList>
+#include <QString>
+
+class AtmDb : public TrustDb, virtual public QXmppAtmTrustStorage
+{
+public:
+ AtmDb(const QString &jid, const JidDb &db);
+
+ QXmppTask<void> addKeysForPostponedTrustDecisions(
+ const QString &encryption, const QByteArray &senderKeyId,
+ const QList<QXmppTrustMessageKeyOwner> &keyOwners) override;
+ QXmppTask<void> removeKeysForPostponedTrustDecisions(
+ const QString &encryption,
+ const QList<QByteArray> &keyIdsForAuthentication,
+ const QList<QByteArray> &keyIdsForDistrusting) override;
+ QXmppTask<void> removeKeysForPostponedTrustDecisions(
+ const QString &encryption,
+ const QList<QByteArray> &senderKeyIds) override;
+ QXmppTask<void> removeKeysForPostponedTrustDecisions(
+ const QString &encryption) override;
+ QXmppTask<QHash<bool, QMultiHash<QString, QByteArray>>>
+ keysForPostponedTrustDecisions(const QString &encryption,
+ const QList<QByteArray> &senderKeyIds = {}) override;
+};
+
+#endif
diff --git a/client.cpp b/client.cpp
new file mode 100644
index 0000000..3cb4e86
--- /dev/null
+++ b/client.cpp
@@ -0,0 +1,61 @@
+#include "client.h"
+#include <QXmppTask.h>
+#include <QDebug>
+#include <iostream>
+
+Client::Client(const QString &jid, QObject *const parent) :
+ QXmppClient(parent),
+ jid(jid),
+ db(this->jid),
+ atm_db(this->jid, db),
+ atm(&atm_db),
+ omemo_db(db),
+ omemo(&omemo_db)
+{
+ addExtension(&atm);
+ addExtension(&carbon);
+ addExtension(&mam);
+ addExtension(&pubsub);
+ addExtension(&omemo);
+
+ connect(this, &QXmppClient::connected, this,
+ [this]()
+ {
+ omemo.setUp().then(this,
+ [](const bool &&result)
+ {
+ qDebug() << "setUp result: " << result;
+ });
+ });
+
+ omemo.setSecurityPolicy(QXmpp::TrustSecurityPolicy::Toakafa);
+ omemo.load().then(this,
+ [=](const bool &&result)
+ {
+ qDebug() << "load result: " << result;
+ });
+
+ logger()->setLoggingType(QXmppLogger::SignalLogging);
+ logger()->setMessageTypes(QXmppLogger::DebugMessage);
+
+ connect(logger(), &QXmppLogger::message, this,
+ [=] (QXmppLogger::MessageType type, const QString &text)
+ {
+ std::cerr << qPrintable(text) << std::endl;
+ });
+}
+
+QString Client::jidBare()
+{
+ return configuration().jidBare();
+}
+
+const JidDb &Client::database() const
+{
+ return db;
+}
+
+JidDb &Client::database()
+{
+ return db;
+}
diff --git a/client.h b/client.h
new file mode 100644
index 0000000..3da53e8
--- /dev/null
+++ b/client.h
@@ -0,0 +1,40 @@
+#ifndef CLIENT_H
+#define CLIENT_H
+
+#include "atm_db.h"
+#include "jiddb.h"
+#include "omemo_db.h"
+#include "trust_db.h"
+#include <QObject>
+#include <QString>
+#include <QXmppAtmManager.h>
+#include <QXmppCarbonManagerV2.h>
+#include <QXmppClient.h>
+#include <QXmppMamManager.h>
+#include <QXmppOmemoManager.h>
+#include <QXmppOmemoStorage.h>
+#include <QXmppPubSubManager.h>
+#include <QXmppTrustManager.h>
+#include <QXmppTrustStorage.h>
+
+class Client : public QXmppClient
+{
+public:
+ Client(const QString &jid, QObject *parent = nullptr);
+ QString jidBare();
+ const JidDb &database() const;
+ JidDb &database();
+
+private:
+ QXmppCarbonManagerV2 carbon;
+ QXmppMamManager mam;
+ const QString jid;
+ JidDb db;
+ AtmDb atm_db;
+ QXmppAtmManager atm;
+ QXmppPubSubManager pubsub;
+ OmemoDb omemo_db;
+ QXmppOmemoManager omemo;
+};
+
+#endif
diff --git a/credentials.cpp b/credentials.cpp
new file mode 100644
index 0000000..5cd6e6f
--- /dev/null
+++ b/credentials.cpp
@@ -0,0 +1,218 @@
+#include "credentials.h"
+#include <qt5keychain/keychain.h>
+#include <QXmppPromise.h>
+#include <QXmppConfiguration.h>
+#include <iostream>
+
+static const QString service = "xxcc", sep = ";";
+
+QXmppTask<Credentials::PairListResult> Credentials::load(
+ Credentials::PairList &pairs,
+ QStringList::const_iterator &it,
+ QStringList::const_iterator end)
+{
+ if (it == end)
+ return QXmpp::Private::makeReadyTask<PairListResult>(
+ PairListResult(pairs));
+
+ QXmppPromise<PairListResult> promise;
+
+ load(*it).then(this,
+ [=] (LoadResult &&result) mutable
+ {
+ if (std::holds_alternative<QString>(result))
+ {
+ pairs << Pair(*it, std::get<QString>(result));
+
+ load(pairs, ++it, end).then(this,
+ [=] (PairListResult &&result) mutable
+ {
+ promise.finish(result);
+ });
+ }
+ else if (std::holds_alternative<Error>(result))
+ promise.finish(Error{std::get<Error>(result)});
+ });
+
+ return promise.task();
+}
+
+QXmppTask<Credentials::PairListResult> Credentials::load()
+{
+ QXmppPromise<PairListResult> promise;
+
+ storedUsers().then(this,
+ [=] (Users &&result) mutable
+ {
+ if (std::holds_alternative<QStringList>(result))
+ {
+ PairList pairs;
+ auto list = new QStringList(std::get<QStringList>(result));
+ auto begin = list->cbegin();
+ const auto end = list->cend();
+
+ load(pairs, begin, end).then(this,
+ [=] (PairListResult &&result) mutable
+ {
+ delete list;
+ promise.finish(result);
+ });
+ }
+ else if (std::holds_alternative<Error>(result))
+ promise.finish(Error{std::get<Error>(result)});
+ });
+
+ return promise.task();
+}
+
+QXmppTask<Credentials::StoreResult> Credentials::store(Client *c)
+{
+ QXmppPromise<StoreResult> promise;
+ const QXmppConfiguration &cfg = c->configuration();
+ const QString user = cfg.jidBare();
+
+ store(user, cfg.password()).then(this,
+ [=] (StoreResult &&result) mutable
+ {
+ store(user).then(this,
+ [=] (StoreResult &&result) mutable
+ {
+ promise.finish(result);
+ });
+ });
+
+ return promise.task();
+}
+
+QXmppTask<Credentials::StoreResult> Credentials::store(const QString &user)
+{
+ QXmppPromise<StoreResult> promise;
+
+ storedUsersList().then(this,
+ [=] (LoadResult &&result) mutable
+ {
+ if (std::holds_alternative<QString>(result))
+ {
+ auto &list = std::get<QString>(result);
+
+ if (!list.isEmpty())
+ list += sep;
+
+ list += user;
+ store("users", list).then(this,
+ [=] (StoreResult &&result) mutable
+ {
+ promise.finish(result);
+ });
+ }
+ else if (std::holds_alternative<Error>(result))
+ {
+ store("users", user).then(this,
+ [=] (StoreResult &&result) mutable
+ {
+ promise.finish(result);
+ });
+ }
+ });
+
+ return promise.task();
+}
+
+QXmppTask<Credentials::LoadResult> Credentials::storedUsersList()
+{
+ QXmppPromise<LoadResult> promise;
+
+ load("users").then(this,
+ [=] (LoadResult &&result) mutable
+ {
+ promise.finish(result);
+ });
+
+ return promise.task();
+
+}
+
+QXmppTask<Credentials::LoadResult> Credentials::load(const QString &key)
+{
+ QXmppPromise<LoadResult> promise;
+ auto job = new QKeychain::ReadPasswordJob(service);
+
+ job->setKey(key);
+ job->connect(job, &QKeychain::Job::finished, this,
+ [=] (QKeychain::Job *const job) mutable
+ {
+ auto readjob = dynamic_cast<QKeychain::ReadPasswordJob *>(job);
+
+ if (readjob->error())
+ {
+ const auto error = "Failed to load " + key + ": "
+ + readjob->errorString();
+
+ std::cerr << qPrintable(error) << std::endl;
+ promise.finish(Error{error});
+ }
+ else
+ {
+ auto users = readjob->textData();
+
+ promise.finish(QString(users));
+ }
+ });
+
+ job->start();
+ return promise.task();
+}
+
+QXmppTask<Credentials::StoreResult> Credentials::store(const QString &key,
+ const QString &value)
+{
+ QXmppPromise<StoreResult> promise;
+ auto job = new QKeychain::WritePasswordJob(service);
+
+ job->setKey(key);
+ job->setTextData(value);
+ job->connect(job, &QKeychain::Job::finished, this,
+ [=] (QKeychain::Job *const job) mutable
+ {
+ if (job->error())
+ {
+ StoreResult sr;
+
+ const auto error = "Failed to store " + key + ": "
+ + job->errorString();
+
+ std::cerr << qPrintable(error) << std::endl;
+ sr = Error{error};
+ promise.finish(StoreResult(sr));
+ }
+ else
+ promise.finish(Success{});
+ });
+
+ job->start();
+ return promise.task();
+}
+
+QXmppTask<Credentials::Users> Credentials::storedUsers()
+{
+ QXmppPromise<Users> promise;
+
+ storedUsersList().then(this,
+ [=] (LoadResult &&result) mutable
+ {
+ if (std::holds_alternative<QString>(result))
+ {
+ const auto &value = std::get<QString>(result);
+
+ promise.finish(QStringList(value.split(sep)));
+ }
+ else if (std::holds_alternative<Error>(result))
+ {
+ const auto &error = std::get<Error>(result);
+
+ promise.finish(Error{error.description});
+ }
+ });
+
+ return promise.task();
+}
diff --git a/credentials.h b/credentials.h
new file mode 100644
index 0000000..100729b
--- /dev/null
+++ b/credentials.h
@@ -0,0 +1,45 @@
+#ifndef CREDENTIALS_H
+#define CREDENTIALS_H
+
+#include "client.h"
+#include <QXmppTask.h>
+#include <QXmppPromise.h>
+#include <QObject>
+#include <QList>
+#include <QPair>
+#include <QStringList>
+#include <variant>
+
+class Credentials : public QObject
+{
+ Q_OBJECT
+public:
+ struct Success {};
+ struct Error
+ {
+ QString description;
+ };
+
+ using Pair = QPair<QString, QString>;
+ using PairList = QList<Pair>;
+ using StoreResult = std::variant<Success, Error>;
+ using LoadResult = std::variant<QString, Error>;
+ using PairListResult = std::variant<PairList, Error>;
+
+ QXmppTask<PairListResult> load();
+ QXmppTask<StoreResult> store(Client *c);
+
+private:
+ using Users = std::variant<QStringList, Error>;
+
+ QXmppTask<StoreResult> store(const QString &key, const QString &value);
+ QXmppTask<StoreResult> store(const QString &user);
+ QXmppTask<LoadResult> load(const QString &key);
+ QXmppTask<LoadResult> storedUsersList();
+ QXmppTask<Users> storedUsers();
+ QXmppTask<PairListResult> load(PairList &pairs,
+ QStringList::const_iterator &it,
+ QStringList::const_iterator end);
+};
+
+#endif
diff --git a/direction.h b/direction.h
new file mode 100644
index 0000000..aed3bdf
--- /dev/null
+++ b/direction.h
@@ -0,0 +1,6 @@
+#ifndef DIRECTION_H
+#define DIRECTION_H
+
+enum Direction {In, Out};
+
+#endif
diff --git a/jiddb.cpp b/jiddb.cpp
new file mode 100644
index 0000000..53d77e0
--- /dev/null
+++ b/jiddb.cpp
@@ -0,0 +1,912 @@
+#include "jiddb.h"
+#include <QDir>
+#include <QSqlQuery>
+#include <QSqlError>
+#include <QStandardPaths>
+#include <QTimeZone>
+#include <QVariant>
+#include <iostream>
+#include <stdexcept>
+
+JidDb::JidDb(const QString &jid) :
+ jid(jid),
+ db(QSqlDatabase::addDatabase("QSQLITE"))
+{
+ const auto path = QStandardPaths::writableLocation(
+ QStandardPaths::AppDataLocation);
+ const QDir dir;
+
+ if (!dir.exists(path) && !dir.mkdir(path))
+ throw std::runtime_error("Failed to create app dir");
+
+ const auto abspath = path + "/" + jid + ".db";
+
+ db.setDatabaseName(abspath);
+
+ if (!db.open())
+ throw std::runtime_error(qPrintable("database" + abspath
+ + "could not be created"));
+
+ QSqlQuery q(db);
+
+ if (!q.exec("create table if not exists roster "
+ "(jid TEXT unique) strict;"))
+ std::cerr << "JidDb::JidDb: query exec failed" << std::endl;
+}
+
+int JidDb::addToRoster(const QString &jid)
+{
+ QSqlQuery q(db);
+
+ if (!q.exec("insert or ignore into roster (jid) values ('" + jid + "');"))
+ {
+ std::cerr << "JidDb::addToRoster: query exec failed" << std::endl;
+ return -1;
+ }
+
+ Q_EMIT addedToRoster(jid);
+ return 0;
+}
+
+int JidDb::addToRoster(const QStringList &roster)
+{
+ for (const auto &r : roster)
+ if (addToRoster(r))
+ return -1;
+
+ return 0;
+}
+
+QStringList JidDb::roster() const
+{
+ QStringList ret;
+ QSqlQuery q(db);
+
+ if (!q.exec("select jid from roster;"))
+ std::cerr << "query exec failed" << std::endl;
+ else
+ while (q.next())
+ ret.append(q.value("jid").toString());
+
+ return ret;
+}
+
+int JidDb::ensureContactTable(const QString &jid) const
+{
+ QSqlQuery q(db);
+
+ if (!q.exec("create table if not exists '" + jid +
+ "' (direction TEXT, time INTEGER, body TEXT) strict;"))
+ {
+ std::cerr << "JidDb::storeMessage: query exec failed" << std::endl;
+ return -1;
+ }
+
+ return 0;
+}
+
+static QString securityPolicyTableName(const QString &encryption)
+{
+ return "securityPolicy/" + encryption;
+}
+
+int JidDb::ensureSecurityPolicyTable(const QString &encryption,
+ QString &table) const
+{
+ QSqlQuery q(db);
+
+ table = securityPolicyTableName(encryption);
+
+ if (!q.exec("create table if not exists '" + table
+ + "' (policy TEXT unique) strict;"))
+ {
+ std::cerr << "JidDb::ensureSecurityPolicyTable: query exec failed"
+ << std::endl;
+ return -1;
+ }
+
+ return 0;
+}
+
+static QString keysTableName(const QString &encryption)
+{
+ return "keys/" + encryption;
+}
+
+int JidDb::ensureKeysTable(const QString &encryption, QString &table) const
+{
+ QSqlQuery q(db);
+
+ table = keysTableName(encryption);
+
+ if (!q.exec("create table if not exists '" + table
+ + "' (owner TEXT, trustlevel TEXT, key TEXT) strict;"))
+ {
+ std::cerr << "JidDb::ensureKeysTable: query exec failed: "
+ << qPrintable(q.lastError().text()) << std::endl;
+ return -1;
+ }
+
+ return 0;
+}
+
+QList<JidDb::Message> JidDb::messages(const QString &jid,
+ const int tail) const
+{
+ QSqlQuery q(db);
+
+ if (ensureContactTable(jid))
+ return QList<Message>();
+ else if (tail < 0)
+ {
+ if (!q.exec("select * from '" + jid + "' order by time;"))
+ {
+ std::cerr << "JidDb::messages: query exec failed: "
+ << qPrintable(q.lastError().text()) << std::endl;
+ return QList<Message>();
+ }
+ }
+ else if (!q.exec("select * from '" + jid + "' order by time desc limit "
+ + QString::number(tail) + ";"))
+ {
+ std::cerr << "JidDb::messages: query exec failed: "
+ << qPrintable(q.lastError().text()) << std::endl;
+ return QList<Message>();
+ }
+
+ QList<Message> ret;
+
+ while (q.next())
+ {
+ Message m;
+ bool ok;
+ qlonglong t = q.value("time").toLongLong(&ok);
+
+ if (!ok)
+ {
+ std::cerr << "JidDb::messages: invalid time" << std::endl;
+ // Attempt to read other messages.
+ continue;
+ }
+
+ m.body = q.value("body").toString();
+ m.dt = QDateTime::fromSecsSinceEpoch(t, QTimeZone::systemTimeZone());
+
+ const auto dirstr = q.value("direction").toString();
+
+ if (dirstr == "in")
+ m.direction = Direction::In;
+ else if (dirstr == "out")
+ m.direction = Direction::Out;
+ else
+ {
+ std::cerr << "JidDb::messages: invalid direction" << std::endl;
+ // Attempt to read other messages.
+ continue;
+ }
+
+ ret << m;
+ }
+
+ return ret;
+}
+
+int JidDb::store(const JidDb::Message &msg) const
+{
+ QSqlQuery q(db);
+ QString dir;
+
+ switch (msg.direction)
+ {
+ case Direction::In:
+ dir = "in";
+ break;
+
+ case Direction::Out:
+ dir = "out";
+ break;
+ }
+
+ ensureContactTable(msg.contact);
+
+ if (!q.exec("insert into '" + msg.contact
+ + "' (direction, time, body) values "
+ "('" + dir + "', "
+ + QString::number(msg.dt.toSecsSinceEpoch()) + ", "
+ "'" + msg.body + "')"))
+ {
+ std::cerr << "JidDb::storeMessage: query exec 2 failed" << std::endl;
+ return -1;
+ }
+
+ return 0;
+}
+
+QStringList JidDb::tables() const
+{
+ QSqlQuery q(db);
+
+ if (!q.exec("select name from sqlite_schema where "
+ "type = 'table' and name not like 'sqlite_%'"))
+ {
+ std::cerr << "JidDb::conversations: query failed" << std::endl;
+ return QStringList();
+ }
+
+ QStringList ret;
+
+ while (q.next())
+ ret << q.value("name").toString();
+
+ return ret;
+}
+
+QList<JidDb::Conversation> JidDb::conversations() const
+{
+ const auto conversations = tables();
+ QList<Conversation> ret;
+
+ for (const auto &jid : conversations)
+ if (jid.contains('@'))
+ {
+ const auto msgs = messages(jid, 1);
+
+ if (!msgs.isEmpty())
+ {
+ const auto &m = msgs.first();
+
+ ret << Conversation(jid, m.body, m.dt);
+ }
+ }
+
+ return ret;
+}
+
+QString JidDb::securityPolicy(const QString &encryption) const
+{
+ const auto tb = tables();
+ const auto name = securityPolicyTableName(encryption);
+
+ for (const auto &t : tb)
+ {
+ if (t == name)
+ {
+ QStringList ret;
+ QSqlQuery q(db);
+
+ if (!q.exec("select policy from '" + name + "';"))
+ std::cerr << "query exec failed" << std::endl;
+ else
+ while (q.next())
+ return q.value("policy").toString();
+
+ break;
+ }
+ }
+
+ return QString();
+}
+
+std::optional<QXmppOmemoStorage::OwnDevice> JidDb::omemoOwnDevice() const
+{
+ return std::optional<QXmppOmemoStorage::OwnDevice>();
+}
+
+int JidDb::store(const QString &encryption, const QString &policy) const
+{
+ QString table;
+
+ if (ensureSecurityPolicyTable(encryption, table))
+ return -1;
+
+ QSqlQuery q(db);
+
+ if (!q.exec("insert or ignore into '" + table
+ + "' (policy) values ('" + policy + "');"))
+ {
+ std::cerr << "JidDb::store: query exec failed"
+ << std::endl;
+ return -1;
+ }
+
+ return 0;
+}
+
+QList<JidDb::Keys> JidDb::keys(const QString &encryption) const
+{
+ QList<Keys> ret;
+ QString table;
+
+ if (ensureKeysTable(encryption, table))
+ {
+ std::cerr << "JidDb::keys: ensureKeysTable failed" << std::endl;
+ return QList<Keys>();
+ }
+
+ QSqlQuery q(db);
+
+ if (!q.exec("select owner, trustlevel, key from '" + table + "';"))
+ std::cerr << "JidDb::keys: query exec failed" << std::endl;
+ else
+ while (q.next())
+ {
+ const auto owner = q.value("owner").toString();
+ const auto trust_level = q.value("trustlevel").toString();
+ const auto key_b64 = q.value("key").toString();
+ const auto key = QByteArray::fromBase64(key_b64.toLatin1());
+ bool found = false;
+
+ for (auto &k : ret)
+ if (k.owner == owner)
+ {
+ k.keys.append(key);
+ found = true;
+ break;
+ }
+
+ if (!found)
+ {
+ Keys keys;
+
+ keys.owner = owner;
+ keys.trust_level = trust_level;
+ keys.keys.append(key);
+ ret.append(keys);
+ }
+ }
+
+ return ret;
+}
+
+int JidDb::store(const QString &encryption, const JidDb::Keys &keys) const
+{
+ QString table;
+
+ if (ensureKeysTable(encryption, table))
+ {
+ std::cerr << "JidDb::store: ensureKeysTable failed" << std::endl;
+ return -1;
+ }
+
+ for (const auto &k : keys.keys)
+ {
+ QSqlQuery q(db);
+
+ const auto kstr = k.isEmpty() ? "\"\""
+ : "'" + QString::fromLatin1(k.toBase64()) + "'";
+
+ if (!q.exec("insert or replace into '" + table
+ + "' (owner, trustlevel, key) values ("
+ "'" + keys.owner + "', "
+ "'" + keys.trust_level + "',"
+ + kstr
+ + ");"))
+ {
+ std::cerr << "JidDb::store: query exec failed: "
+ << qPrintable(q.lastError().text()) << std::endl;
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int JidDb::ensureOmemoDeviceTable() const
+{
+ QSqlQuery q(db);
+
+ if (!q.exec("create table if not exists 'omemo/devices' "
+ "(jid TEXT, deviceID INTEGER, keyID TEXT, session TEXT, "
+ "unresp_sent INTEGER, unresp_recv INTEGER, removal INTEGER) "
+ "strict;"))
+ {
+ std::cerr << "JidDb::ensureOmemoTable: query exec failed: "
+ << qPrintable(q.lastError().text()) << std::endl;
+ return -1;
+ }
+
+ return 0;
+}
+
+int JidDb::store(const QString &jid, const uint32_t id,
+ const QXmppOmemoStorage::Device &d) const
+{
+ if (ensureOmemoDeviceTable())
+ {
+ std::cerr << "JidDb::store: ensureOmemoTable failed" << std::endl;
+ return -1;
+ }
+
+ QSqlQuery q(db);
+ const auto keyId = d.keyId.isEmpty() ? "\"\""
+ : "'" + QString::fromLatin1(d.keyId.toBase64()) + "'",
+ session = d.session.isEmpty() ? "\"\""
+ : "'" + QString::fromLatin1(d.session.toBase64()) + "'",
+ query = "insert or replace into 'omemo/devices' "
+ "(jid, deviceID, keyID, session, "
+ "unresp_sent, unresp_recv , removal) values ("
+ "'" + jid + "', "
+ + QString::number(id) + ", "
+ + keyId + ", "
+ + session + ", "
+ + QString::number(d.unrespondedSentStanzasCount) + ", "
+ + QString::number(d.unrespondedReceivedStanzasCount) + ", "
+ + QString::number(d.removalFromDeviceListDate.toSecsSinceEpoch())
+ + ");";
+
+ if (!q.exec(query))
+ {
+ std::cerr << "JidDb::store(id, jid, d): query exec failed: "
+ << qPrintable(q.lastError().text()) << std::endl;
+ return -1;
+ }
+
+ return 0;
+}
+
+int JidDb::ensureOmemoSignedPreKeyTable() const
+{
+ QSqlQuery q(db);
+
+ if (!q.exec("create table if not exists 'omemo/signedprekeys' "
+ "(id INTEGER, creation INTEGER, data TEXT) strict;"))
+ {
+ std::cerr << "JidDb::ensureOmemoSignedPreKeyTable: query exec failed: "
+ << qPrintable(q.lastError().text()) << std::endl;
+ return -1;
+ }
+
+ return 0;
+}
+
+int JidDb::store(uint32_t id, const QXmppOmemoStorage::SignedPreKeyPair &spk)
+ const
+{
+ if (ensureOmemoSignedPreKeyTable())
+ {
+ std::cerr << "JidDb::store: ensureOmemoSignedPreKeyTable failed"
+ << std::endl;
+ return -1;
+ }
+
+ QSqlQuery q(db);
+ const auto data = spk.data.isEmpty() ? "\"\""
+ : "'" + QString::fromLatin1(spk.data.toBase64()) + "'",
+ query = "insert or replace into 'omemo/signedprekeys' "
+ "(id, creation, data) values ("
+ + QString::number(id) + ", "
+ + QString::number(spk.creationDate.toSecsSinceEpoch()) + ", "
+ + data
+ + ");";
+
+ if (!q.exec(query))
+ {
+ std::cerr << "JidDb::store(spk): query exec failed: "
+ << qPrintable(q.lastError().text()) << std::endl;
+ return -1;
+ }
+
+ return 0;
+}
+
+int JidDb::ensureOmemoPreKeyTable() const
+{
+ QSqlQuery q(db);
+
+ if (!q.exec("create table if not exists 'omemo/prekeys' "
+ "(id INTEGER, data TEXT) strict;"))
+ {
+ std::cerr << "JidDb::ensureOmemoPreKeyTable: query exec failed: "
+ << qPrintable(q.lastError().text()) << std::endl;
+ return -1;
+ }
+
+ return 0;
+}
+
+int JidDb::store(const QHash<uint32_t, QByteArray> &pairs) const
+{
+ if (ensureOmemoPreKeyTable())
+ {
+ std::cerr << "JidDb::store: ensureOmemoPreKeyTable failed"
+ << std::endl;
+ return -1;
+ }
+
+ for (const auto id : pairs.keys())
+ {
+ const auto data = pairs.value(id);
+ const auto datastr = data.isEmpty() ? "\"\""
+ : "'" + QString::fromLatin1(data.toBase64()) + "'",
+ query = "insert or replace into 'omemo/prekeys' "
+ "(id, data) values ("
+ + QString::number(id) + ", "
+ + datastr
+ + ");";
+
+ QSqlQuery q(db);
+
+ if (!q.exec(query))
+ {
+ std::cerr << "JidDb::store(pairs): query exec failed: "
+ << qPrintable(q.lastError().text()) << std::endl;
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int JidDb::ensureOmemoOwnDeviceTable() const
+{
+ QSqlQuery q(db);
+
+ if (!q.exec("create table if not exists 'omemo/owndevice' "
+ "(id INTEGER, label TEXT, privkey TEXT, pubkey TEXT, "
+ "latestSignedPreKeyId INTEGER, latestPreKeyId INTEGER) strict;"))
+ {
+ std::cerr << "JidDb::ensureOmemoOwnDeviceTable: query exec failed: "
+ << qPrintable(q.lastError().text()) << std::endl;
+ return -1;
+ }
+
+ return 0;
+}
+
+int JidDb::store(const QXmppOmemoStorage::OwnDevice &own) const
+{
+ if (ensureOmemoOwnDeviceTable())
+ {
+ std::cerr << "JidDb::store: ensureOmemoOwnDeviceTable failed"
+ << std::endl;
+ return -1;
+ }
+
+ QSqlQuery q(db);
+ const auto privkey = own.privateIdentityKey.isEmpty() ? "\"\""
+ : "'" + QString::fromLatin1(own.privateIdentityKey.toBase64()) + "'",
+ pubkey = own.publicIdentityKey.isEmpty() ? "\"\""
+ : "'" + QString::fromLatin1(own.publicIdentityKey.toBase64()) + "'",
+ query = "insert or replace into 'omemo/owndevice' "
+ "(id, label, privkey, pubkey, latestSignedPreKeyId, "
+ "latestPreKeyId) values ("
+ + QString::number(own.id) + ", "
+ "'" + own.label + "', "
+ + privkey + ", "
+ + pubkey + ", "
+ + QString::number(own.latestSignedPreKeyId) + ", "
+ + QString::number(own.latestPreKeyId)
+ + ");";
+
+ if (!q.exec(query))
+ {
+ std::cerr << "JidDb::store(own): query exec failed: "
+ << qPrintable(q.lastError().text()) << std::endl;
+ std::cerr << "Query was: " << qPrintable(query) << std::endl;
+ return -1;
+ }
+
+ return 0;
+}
+
+QList<JidDb::OmemoDevice> JidDb::omemoDevices() const
+{
+ QList<OmemoDevice> ret;
+ QSqlQuery q(db);
+
+ if (!q.exec("select * from 'omemo/devices';"))
+ {
+ std::cerr << "JidDb::omemoDevices: query exec failed: "
+ << qPrintable(q.lastError().text()) << std::endl;
+ return QList<OmemoDevice>();
+ }
+
+ while (q.next())
+ {
+ QXmppOmemoStorage::Device device;
+ bool ok;
+ const uint32_t deviceId = q.value("deviceID").toULongLong(&ok);
+
+ if (!ok)
+ {
+ std::cerr << "JidDb::messages: invalid deviceID" << std::endl;
+ // Attempt to read other messages.
+ continue;
+ }
+
+ device.unrespondedReceivedStanzasCount =
+ q.value("unresp_recv").toInt(&ok);
+
+ if (!ok)
+ {
+ std::cerr << "JidDb::messages: invalid unresp_recv" << std::endl;
+ // Attempt to read other messages.
+ continue;
+ }
+
+ device.unrespondedSentStanzasCount = q.value("unresp_sent").toInt(&ok);
+
+ if (!ok)
+ {
+ std::cerr << "JidDb::messages: invalid unresp_sent" << std::endl;
+ // Attempt to read other messages.
+ continue;
+ }
+
+ const QByteArray keyId = q.value("keyID").toByteArray(),
+ session = q.value("session").toByteArray();
+ const auto jid = q.value("jid").toString();
+ bool found = false;
+
+ for (auto &r : ret)
+ if (jid == r.jid)
+ {
+ r.devices.insert(deviceId, device);
+ break;
+ }
+
+ if (!found)
+ {
+ OmemoDevice d;
+
+ d.jid = jid;
+ d.devices.insert(deviceId, device);
+ ret << d;
+ }
+ }
+
+ return ret;
+}
+
+QHash<uint32_t, QXmppOmemoStorage::SignedPreKeyPair> JidDb::signedPreKeyPairs()
+ const
+{
+ QHash<uint32_t, QXmppOmemoStorage::SignedPreKeyPair> ret;
+
+ QSqlQuery q(db);
+
+ if (!q.exec("select * from 'omemo/signedprekeys';"))
+ {
+ std::cerr << "JidDb::signedPreKeyPairs: query exec failed: "
+ << qPrintable(q.lastError().text()) << std::endl;
+ return QHash<uint32_t, QXmppOmemoStorage::SignedPreKeyPair>();
+ }
+
+ while (q.next())
+ {
+ QXmppOmemoStorage::SignedPreKeyPair spk;
+ bool ok;
+ const uint32_t id = q.value("id").toULongLong(&ok);
+
+ if (!ok)
+ {
+ std::cerr << "JidDb::messages: invalid id" << std::endl;
+ // Attempt to read other entries.
+ continue;
+ }
+
+ const auto creation = q.value("creation").toInt(&ok);
+
+ if (!ok)
+ {
+ std::cerr << "JidDb::messages: invalid creation date" << std::endl;
+ // Attempt to read other entries.
+ continue;
+ }
+
+ spk.creationDate = QDateTime::fromSecsSinceEpoch(creation);
+ spk.data = q.value("data").toString().toLatin1().toBase64();
+ ret.insert(id, spk);
+ }
+
+ return ret;
+}
+
+QHash<uint32_t, QByteArray> JidDb::preKeyPairs() const
+{
+ QHash<uint32_t, QByteArray> ret;
+
+ QSqlQuery q(db);
+
+ if (!q.exec("select * from 'omemo/signedprekeys';"))
+ {
+ std::cerr << "JidDb::signedPreKeyPairs: query exec failed: "
+ << qPrintable(q.lastError().text()) << std::endl;
+ return QHash<uint32_t, QByteArray>();
+ }
+
+ while (q.next())
+ {
+ bool ok;
+ const uint32_t id = q.value("id").toULongLong(&ok);
+
+ if (!ok)
+ {
+ std::cerr << "JidDb::messages: invalid id" << std::endl;
+ // Attempt to read other entries.
+ continue;
+ }
+
+ const auto data = q.value("data").toString().toLatin1().toBase64();
+ ret.insert(id, data);
+ }
+
+ return ret;
+}
+
+void JidDb::removeKeys(const QString &encryption) const
+{
+ QString table;
+
+ if (ensureKeysTable(encryption, table))
+ std::cerr << "JidDb::removeKeys: ensureKeysTable failed" << std::endl;
+ else
+ {
+ QSqlQuery q(db);
+
+ if (!q.exec("delete from '" + table + "'"))
+ std::cerr << "JidDb::removeKeys: query exec failed: "
+ << qPrintable(q.lastError().text()) << std::endl;
+ }
+}
+
+void JidDb::removeOmemoDevice(const QString &jid, const uint32_t id) const
+{
+ if (ensureOmemoDeviceTable())
+ std::cerr << "JidDb::removeOmemoDevice: ensureOmemoDeviceTable failed"
+ << std::endl;
+ else
+ {
+ QSqlQuery q(db);
+ const auto idstr = QString::number(id);
+
+ if (!q.exec("delete from 'omemo/devices' "
+ "where (jid = '" + jid + "' "
+ "and id = " + idstr + ")"))
+ std::cerr << "JidDb::removeOmemoDevice: query exec failed: "
+ << qPrintable(q.lastError().text()) << std::endl;
+ }
+}
+
+void JidDb::removeOmemoDevices(const QString &jid) const
+{
+ if (ensureOmemoDeviceTable())
+ std::cerr << "JidDb::removeOmemoDevices: ensureOmemoDeviceTable failed"
+ << std::endl;
+ else
+ {
+ QSqlQuery q(db);
+
+ if (!q.exec("delete from 'omemo/devices' "
+ "where jid = '" + jid + "'"))
+ std::cerr << "JidDb::removeOmemoDevices: query exec failed: "
+ << qPrintable(q.lastError().text()) << std::endl;
+ }
+}
+
+int JidDb::ensureOwnIdTable(const QString &encryption, QString &table)
+ const
+{
+ QSqlQuery q(db);
+
+ table = encryption + "/ownID";
+
+ if (!q.exec("create table if not exists '" + table + "' "
+ "(ID TEXT) strict;"))
+ {
+ std::cerr << "JidDb::ensureOwnIdTable: query exec failed: "
+ << qPrintable(q.lastError().text()) << std::endl;
+ return -1;
+ }
+
+ return 0;
+}
+
+QByteArray JidDb::ownKeyId(const QString &encryption) const
+{
+ QString table;
+
+ if (ensureOwnIdTable(encryption, table))
+ {
+ std::cerr << "JidDb::store: ensureOwnIdTable failed" << std::endl;
+ return QByteArray();
+ }
+
+ QSqlQuery q(db);
+
+ if (!q.exec("select * from '" + table + "';"))
+ {
+ std::cerr << "JidDb::ownKeyId: query exec failed: "
+ << qPrintable(q.lastError().text()) << std::endl;
+ return QByteArray();
+ }
+
+ while (q.next())
+ {
+ const auto id = q.value("id").toString();
+
+ return QByteArray::fromBase64(id.toLatin1());
+ }
+
+ return QByteArray();
+}
+
+int JidDb::store(const QString &encryption, const QByteArray &ownId) const
+{
+ QString table;
+
+ if (ensureOwnIdTable(encryption, table))
+ {
+ std::cerr << "JidDb::store: ensureOmemoOwnDeviceTable failed"
+ << std::endl;
+ return -1;
+ }
+
+ QSqlQuery q(db);
+ const auto id = ownId.isEmpty() ? "\"\""
+ : "'" + QString::fromLatin1(ownId.toBase64()) + "'",
+ query = "insert or replace into '" + table + "' "
+ "(id) values ("
+ + id
+ + ");";
+
+ return 0;
+}
+
+void JidDb::removeSignedPreKeyPair(const uint32_t id) const
+{
+ if (ensureOmemoSignedPreKeyTable())
+ std::cerr << "JidDb::removeSignedPreKeyPair: "
+ "ensureOmemoSignedPreKeyTable failed" << std::endl;
+ else
+ {
+ QSqlQuery q(db);
+ const auto idstr = QString::number(id);
+
+ if (!q.exec("delete from 'omemo/signedprekeys' where id = " + idstr))
+ std::cerr << "JidDb::removeSignedPreKeyPair: query exec failed: "
+ << qPrintable(q.lastError().text()) << std::endl;
+ }
+}
+
+void JidDb::removePreKeyPair(const uint32_t id) const
+{
+ if (ensureOmemoPreKeyTable())
+ std::cerr << "JidDb::removePreKeyPair: "
+ "ensureOmemoPreKeyTable failed" << std::endl;
+ else
+ {
+ QSqlQuery q(db);
+ const auto idstr = QString::number(id);
+
+ if (!q.exec("delete from 'omemo/prekeys' where id = " + idstr))
+ std::cerr << "JidDb::removePreKeyPair: query exec failed: "
+ << qPrintable(q.lastError().text()) << std::endl;
+ }
+}
+
+void JidDb::removeOmemo() const
+{
+ if (ensureOmemoSignedPreKeyTable())
+ std::cerr << "JidDb::removeOmemo: "
+ "ensureOmemoSignedPreKeyTable failed" << std::endl;
+ else if (ensureOmemoPreKeyTable())
+ std::cerr << "JidDb::removeOmemo: "
+ "ensureOmemoPreKeyTable failed" << std::endl;
+ else
+ {
+ QSqlQuery q(db);
+
+ if (!q.exec("delete from 'omemo/signedprekeys'"))
+ std::cerr << "JidDb::removeOmemo: query exec failed: "
+ << qPrintable(q.lastError().text()) << std::endl;
+ else
+ {
+ QSqlQuery q(db);
+
+ if (!q.exec("delete from 'omemo/prekeys'"))
+ std::cerr << "JidDb::removeOmemo: query exec failed: "
+ << qPrintable(q.lastError().text()) << std::endl;
+ }
+ }
+}
diff --git a/jiddb.h b/jiddb.h
new file mode 100644
index 0000000..6c9dcb8
--- /dev/null
+++ b/jiddb.h
@@ -0,0 +1,114 @@
+#ifndef JID_DB_H
+#define JID_DB_H
+
+#include "direction.h"
+#include <QByteArray>
+#include <QDateTime>
+#include <QList>
+#include <QObject>
+#include <QPair>
+#include <QString>
+#include <QStringList>
+#include <QSqlDatabase>
+#include <QXmppOmemoStorage.h>
+#include <QVariant>
+#include <optional>
+
+class JidDb : public QObject
+{
+ Q_OBJECT
+
+public:
+ struct Message
+ {
+ Direction direction;
+ QDateTime dt;
+ QString contact, body;
+ };
+
+ struct Conversation
+ {
+ Conversation(const QString &to, const QString &last_msg,
+ const QDateTime &dt) :
+ to(to), last_msg(last_msg), dt(dt) {}
+ QString to, last_msg;
+ QDateTime dt;
+ };
+
+ struct Trust
+ {
+ QString security_policy;
+ };
+
+ struct Keys
+ {
+ Keys() {}
+ Keys(const QString &owner, const QString &trust_level,
+ const QList<QByteArray> &keys) :
+ owner(owner),
+ trust_level(trust_level),
+ keys(keys)
+ {}
+ QString owner, trust_level;
+ QList<QByteArray> keys;
+ };
+
+ struct OmemoDevice
+ {
+ QString jid;
+ QHash<uint32_t, QXmppOmemoStorage::Device> devices;
+ };
+
+ JidDb(const QString &jid);
+ QStringList roster() const;
+ const QString &jid;
+
+public Q_SLOTS:
+ QList<Conversation> conversations() const;
+ QList<Message> messages(const QString &jid,
+ int tail = -1) const;
+ QList<Keys> keys(const QString &encryption) const;
+ QString securityPolicy(const QString &encryption) const;
+ QList<OmemoDevice> omemoDevices() const;
+ std::optional<QXmppOmemoStorage::OwnDevice> omemoOwnDevice() const;
+ QHash<uint32_t, QXmppOmemoStorage::SignedPreKeyPair> signedPreKeyPairs()
+ const;
+ QHash<uint32_t, QByteArray> preKeyPairs() const;
+ QByteArray ownKeyId(const QString &encryption) const;
+ int addToRoster(const QString &jid);
+ int addToRoster(const QStringList &roster);
+ int store(const Message &msg) const;
+ int store(const QString &encryption,
+ const QString &securityPolicy) const;
+ int store(const QString &encryption, const Keys &keys) const;
+ int store(const QString &jid, uint32_t id,
+ const QXmppOmemoStorage::Device &d) const;
+ int store(uint32_t id, const QXmppOmemoStorage::SignedPreKeyPair &spk) const;
+ int store(const QHash<uint32_t, QByteArray> &pairs) const;
+ int store(const QXmppOmemoStorage::OwnDevice &own) const;
+ int store(const QString &encryption, const QByteArray &ownId) const;
+ void removeKeys(const QString &encryption) const;
+ void removeOmemoDevice(const QString &jid, uint32_t id) const;
+ void removeOmemoDevices(const QString &jid) const;
+ void removeOmemo() const;
+ void removeSignedPreKeyPair(uint32_t id) const;
+ void removePreKeyPair(uint32_t id) const;
+
+Q_SIGNALS:
+ void addedToRoster(QString jid);
+
+private:
+ QSqlDatabase db;
+ int ensureSecurityPolicyTable(const QString &encryption,
+ QString &table) const;
+ int ensureContactTable(const QString &jid) const;
+ int ensureKeysTable(const QString &encryption, QString &table) const;
+ int ensureOmemoDeviceTable() const;
+ int ensureOmemoSignedPreKeyTable() const;
+ int ensureOmemoPreKeyTable() const;
+ int ensureOmemoOwnDeviceTable() const;
+ int ensureOwnIdTable(const QString &encryption, QString &table) const;
+ QStringList tables() const;
+};
+
+#endif
diff --git a/libomemo-c/CMakeLists.txt b/libomemo-c/CMakeLists.txt
new file mode 100644
index 0000000..ba52fed
--- /dev/null
+++ b/libomemo-c/CMakeLists.txt
@@ -0,0 +1,23 @@
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(protobuf-c REQUIRED IMPORTED_TARGET libprotobuf-c)
+set(CMAKE_POLICY_DEFAULT_CMP0048 NEW)
+add_subdirectory(libomemo-c)
+target_include_directories(omemo-c PUBLIC
+ $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/libomemo-c/src>
+ $<INSTALL_INTERFACE:libomemo-c/src>)
+target_link_libraries(omemo-c PRIVATE PkgConfig::protobuf-c)
+export(TARGETS omemo-c FILE Findomemo-c.cmake)
+
+install(
+ TARGETS omemo-c
+ EXPORT OmemoCTargets
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+)
+
+install(
+ EXPORT OmemoCTargets
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/libomemo-c"
+ FILE Findomemo-c.cmake
+)
diff --git a/omemo_db.cpp b/omemo_db.cpp
new file mode 100644
index 0000000..c1949c0
--- /dev/null
+++ b/omemo_db.cpp
@@ -0,0 +1,93 @@
+#include "omemo_db.h"
+#include <QXmppConfiguration.h>
+#include <QXmppFutureUtils_p.h>
+#include <QString>
+#include <iostream>
+#include <optional>
+
+static const QString service = "xxcc", key_namespace = "omemo";
+
+OmemoDb::OmemoDb(const JidDb &db) :
+ db(db)
+{
+}
+
+QXmppTask<QXmppOmemoStorage::OmemoData> OmemoDb::allData()
+{
+ QXmppOmemoStorage::OmemoData ret;
+ QHash<QString, QHash<uint32_t, Device>> devices;
+
+ for (const auto &d : db.omemoDevices())
+ ret.devices.insert(d.jid, d.devices);
+
+ ret.ownDevice = db.omemoOwnDevice();
+ ret.preKeyPairs = db.preKeyPairs();
+ ret.signedPreKeyPairs = db.signedPreKeyPairs();
+ return QXmpp::Private::makeReadyTask(QXmppOmemoStorage::OmemoData(ret));
+}
+
+QXmppTask<void> OmemoDb::setOwnDevice(const std::optional<OwnDevice> &device)
+{
+ if (device.has_value() && db.store(device.value()))
+ std::cerr << "db.store failed" << std::endl;
+
+ return QXmpp::Private::makeReadyTask();
+}
+
+QXmppTask<void> OmemoDb::addSignedPreKeyPair(const uint32_t keyId,
+ const SignedPreKeyPair &keyPair)
+{
+ if (db.store(keyId, keyPair))
+ std::cerr << "db.store failed" << std::endl;
+
+ return QXmpp::Private::makeReadyTask();
+}
+
+QXmppTask<void> OmemoDb::removeSignedPreKeyPair(uint32_t keyId)
+{
+ db.removeSignedPreKeyPair(keyId);
+ return QXmpp::Private::makeReadyTask();
+}
+
+QXmppTask<void> OmemoDb::addPreKeyPairs(
+ const QHash<uint32_t, QByteArray> &keyPairs)
+{
+ if (db.store(keyPairs))
+ std::cerr << "db.store failed" << std::endl;
+
+ return QXmpp::Private::makeReadyTask();
+}
+
+QXmppTask<void> OmemoDb::removePreKeyPair(const uint32_t keyId)
+{
+ db.removePreKeyPair(keyId);
+ return QXmpp::Private::makeReadyTask();
+}
+
+QXmppTask<void> OmemoDb::addDevice(const QString &jid,
+ const uint32_t deviceId, const Device &d)
+{
+ if (db.store(jid, deviceId, d))
+ std::cerr << "db.store failed" << std::endl;
+
+ return QXmpp::Private::makeReadyTask();
+}
+
+QXmppTask<void> OmemoDb::removeDevice(const QString &jid,
+ const uint32_t deviceId)
+{
+ db.removeOmemoDevice(jid, deviceId);
+ return QXmpp::Private::makeReadyTask();
+}
+
+QXmppTask<void> OmemoDb::removeDevices(const QString &jid)
+{
+ db.removeOmemoDevices(jid);
+ return QXmpp::Private::makeReadyTask();
+}
+
+QXmppTask<void> OmemoDb::resetAll()
+{
+ db.removeOmemo();
+ return QXmpp::Private::makeReadyTask();
+}
diff --git a/omemo_db.h b/omemo_db.h
new file mode 100644
index 0000000..da3fd93
--- /dev/null
+++ b/omemo_db.h
@@ -0,0 +1,42 @@
+#ifndef OMEMO_DB_H
+#define OMEMO_DB_H
+
+#include "jiddb.h"
+#include <QXmppGlobal.h>
+#include <QXmppOmemoStorage.h>
+#include <QXmppPromise.h>
+#include <QXmppTask.h>
+#include <qt5keychain/keychain.h>
+#include <QDateTime>
+#include <QByteArray>
+#include <QString>
+#include <QSqlDatabase>
+#include <cstdint>
+
+class OmemoDb : public QObject, public QXmppOmemoStorage
+{
+ Q_OBJECT
+
+public:
+ OmemoDb(const JidDb &db);
+ QXmppTask<OmemoData> allData() override;
+ QXmppTask<void> setOwnDevice(
+ const std::optional<OwnDevice> &device) override;
+ QXmppTask<void> addSignedPreKeyPair(uint32_t keyId,
+ const SignedPreKeyPair &keyPair) override;
+ QXmppTask<void> removeSignedPreKeyPair(uint32_t keyId) override;
+ QXmppTask<void> addPreKeyPairs(
+ const QHash<uint32_t, QByteArray> &keyPairs) override;
+ QXmppTask<void> removePreKeyPair(uint32_t keyId) override;
+ QXmppTask<void> addDevice(const QString &jid,
+ uint32_t deviceId, const Device &device) override;
+ QXmppTask<void> removeDevice(const QString &jid,
+ uint32_t deviceId) override;
+ QXmppTask<void> removeDevices(const QString &jid) override;
+ QXmppTask<void> resetAll() override;
+
+private:
+ const JidDb &db;
+};
+
+#endif
diff --git a/qxmpp/CMakeLists.txt b/qxmpp/CMakeLists.txt
new file mode 100644
index 0000000..0712e3e
--- /dev/null
+++ b/qxmpp/CMakeLists.txt
@@ -0,0 +1,18 @@
+set(BUILD_SHARED OFF)
+set(BUILD_OMEMO ON)
+set(BUILD_TESTS OFF)
+set(WITH_QCA ON)
+set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
+set(QT_VERSION_MAJOR 6)
+add_subdirectory(qxmpp)
+target_link_libraries(${PROJECT_NAME} PRIVATE
+ QXmppQt${QT_VERSION_MAJOR}
+ QXmppOmemoQt${QT_VERSION_MAJOR})
+target_include_directories(QXmppQt${QT_VERSION_MAJOR} PRIVATE
+ ${CMAKE_BINARY_DIR}/qxmpp/qxmpp/src)
+target_include_directories(${PROJECT_NAME} PRIVATE
+ ${CMAKE_BINARY_DIR}/qxmpp/qxmpp/src)
+target_include_directories(QXmppOmemoQt${QT_VERSION_MAJOR} PRIVATE
+ ${CMAKE_BINARY_DIR}/qxmpp/qxmpp/src/omemo)
+target_include_directories(${PROJECT_NAME} PRIVATE
+ ${CMAKE_BINARY_DIR}/qxmpp/qxmpp/src/omemo)
diff --git a/qxmpp/qxmpp b/qxmpp/qxmpp
-Subproject 6fe82239fc55b16953f965ea4e20e5fbfe806dd
+Subproject d7dae544d07016cb27b07fe1a83dbd90729a131
diff --git a/trust_db.cpp b/trust_db.cpp
new file mode 100644
index 0000000..3dfd906
--- /dev/null
+++ b/trust_db.cpp
@@ -0,0 +1,415 @@
+#include "trust_db.h"
+#include <QXmppTrustLevel.h>
+#include <QXmppTrustSecurityPolicy.h>
+#include <QXmppConfiguration.h>
+#include <QXmppFutureUtils_p.h>
+#include <QXmppPromise.h>
+#include <QEventLoop>
+#include <QtConcurrent/QtConcurrent>
+#include <iostream>
+
+TrustDb::TrustDb(const QString &jid, const JidDb &db) :
+ jid(jid),
+ db(db)
+{
+}
+
+static const struct
+{
+ QXmpp::TrustLevel level;
+ const char *str;
+} trustlevels[] =
+{
+ {QXmpp::TrustLevel::Undecided, "Undecided"},
+ {QXmpp::TrustLevel::AutomaticallyDistrusted, "AutomaticallyDistrusted"},
+ {QXmpp::TrustLevel::ManuallyDistrusted, "ManuallyDistrusted"},
+ {QXmpp::TrustLevel::AutomaticallyTrusted, "AutomaticallyTrusted"},
+ {QXmpp::TrustLevel::ManuallyTrusted, "ManuallyTrusted"},
+ {QXmpp::TrustLevel::Authenticated, "Authenticated"}
+};
+
+static const struct
+{
+ QXmpp::TrustSecurityPolicy policy;
+ const char *str;
+} tsp_levels[] =
+{
+ {QXmpp::NoSecurityPolicy, "NoSecurityPolicy"},
+ {QXmpp::Toakafa, "Toakafa"}
+};
+
+static QString toString(const QXmpp::TrustSecurityPolicy securityPolicy)
+{
+ for (const auto &t : tsp_levels)
+ if (t.policy == securityPolicy)
+ return t.str;
+
+ return "unknown";
+}
+
+static QString toString(const QXmpp::TrustLevel &trustLevel)
+{
+ for (const auto &t : trustlevels)
+ if (t.level == trustLevel)
+ return t.str;
+
+ return "unknown";
+}
+
+static int toSecurityPolicy(const QString &s,
+ QXmpp::TrustSecurityPolicy &securityPolicy)
+{
+ for (const auto &t : tsp_levels)
+ if (t.str == s)
+ {
+ securityPolicy = t.policy;
+ return 0;
+ }
+
+ return -1;
+}
+
+static int toTrustLevel(const QString &tstr, QXmpp::TrustLevel &trustLevel)
+{
+ for (const auto &t : trustlevels)
+ if (t.str == tstr)
+ {
+ trustLevel = t.level;
+ return 0;
+ }
+
+ return -1;
+}
+
+QString TrustDb::service() const
+{
+ return "xxcc/trust/" + jid;
+}
+
+QXmppTask<void> TrustDb::setSecurityPolicy(const QString &encryption,
+ const QXmpp::TrustSecurityPolicy securityPolicy)
+{
+ if (db.store(encryption, toString(securityPolicy)))
+ std::cerr << "TrustDb::setSecurityPolicy: store failed"
+ << std::endl;
+
+ return QXmpp::Private::makeReadyTask();
+}
+
+QXmppTask<void> TrustDb::resetSecurityPolicy(const QString &encryption)
+{
+ if (db.store(encryption, QString()))
+ std::cerr << "TrustDb::resetSecurityPolicy: store failed"
+ << std::endl;
+
+ return QXmpp::Private::makeReadyTask();
+}
+
+QXmppTask<QXmpp::TrustSecurityPolicy> TrustDb::securityPolicy(
+ const QString &encryption)
+{
+ const auto s = db.securityPolicy(encryption);
+ QXmpp::TrustSecurityPolicy policy;
+
+ if (toSecurityPolicy(s, policy))
+ std::cerr << "TrustDb::securityPolicy: toSecurityPolicy failed"
+ << std::endl;
+
+ return QXmpp::Private::makeReadyTask(QXmpp::TrustSecurityPolicy(policy));
+}
+
+QXmppTask<void> TrustDb::setOwnKey(const QString &encryption,
+ const QByteArray &keyId)
+{
+ if (db.store(encryption, keyId))
+ std::cerr << "TrustDb::setOwnKey: db.store failed" << std::endl;
+
+ return QXmpp::Private::makeReadyTask();
+}
+
+QXmppTask<void> TrustDb::resetOwnKey(const QString &encryption)
+{
+ return QXmpp::Private::makeReadyTask();
+}
+
+QXmppTask<QByteArray> TrustDb::ownKey(const QString &encryption)
+{
+ return QXmpp::Private::makeReadyTask(db.ownKeyId(encryption));
+}
+
+QXmppTask<void> TrustDb::addKeys(const QString &encryption,
+ const QString &keyOwnerJid, const QList<QByteArray> &keyIds,
+ const QXmpp::TrustLevel trustLevel)
+{
+ const struct JidDb::Keys keys(keyOwnerJid, toString(trustLevel), keyIds);
+
+ if (db.store(encryption, keys))
+ std::cerr << "TrustDb::addKeys: store failed" << std::endl;
+
+ return QXmpp::Private::makeReadyTask();
+}
+
+QXmppTask<void> TrustDb::removeKeys(const QString &encryption,
+ const QList<QByteArray> &keyIds)
+{
+ auto keys = db.keys(encryption);
+
+ for (const auto &id : keyIds)
+ for (auto &k : keys)
+ {
+ const auto i = k.keys.indexOf(id);
+
+ if (i != -1)
+ {
+ k.keys.removeAt(i);
+ break;
+ }
+ }
+
+ for (const auto &k : keys)
+ if (db.store(encryption, k))
+ std::cerr << "TrustDb::removeKeys: store failed" << std::endl;
+
+ return QXmpp::Private::makeReadyTask();
+}
+
+QXmppTask<void> TrustDb::removeKeys(const QString &encryption,
+ const QString &keyOwnerJid)
+{
+ auto keys = db.keys(encryption);
+
+ for (int i = 0; i < keys.count(); i++)
+ if (keys[i].owner == keyOwnerJid)
+ {
+ keys.removeAt(i);
+ break;
+ }
+
+ for (const auto &k : keys)
+ if (db.store(encryption, k))
+ std::cerr << "TrustDb::removeKeys: store failed" << std::endl;
+
+ return QXmpp::Private::makeReadyTask();
+}
+
+QXmppTask<void> TrustDb::removeKeys(const QString &encryption)
+{
+ db.removeKeys(encryption);
+ return QXmpp::Private::makeReadyTask();
+}
+
+QXmppTask<QHash<QXmpp::TrustLevel,
+ QMultiHash<QString, QByteArray>>> TrustDb::keys(const QString &encryption,
+ const QXmpp::TrustLevels trustLevels)
+{
+ QHash<QXmpp::TrustLevel, QMultiHash<QString, QByteArray>> ret;
+ const auto keys = db.keys(encryption);
+
+ for (const auto &k : keys)
+ {
+ QXmpp::TrustLevel level;
+
+ if (toTrustLevel(k.trust_level, level))
+ {
+ std::cerr << "TrustDb::keys: invalid trust level: "
+ << qPrintable(k.trust_level) << std::endl;
+ continue;
+ }
+ else if (!trustLevels.testFlag(level))
+ continue;
+
+ QMultiHash<QString, QByteArray> mh;
+
+ for (const auto &key : k.keys)
+ mh.insert(k.owner, key);
+
+ ret.insert(level, mh);
+ }
+
+ return QXmpp::Private::makeReadyTask(QHash<QXmpp::TrustLevel,
+ QMultiHash<QString, QByteArray>>(ret));
+}
+
+QXmppTask<QHash<QString,
+ QHash<QByteArray, QXmpp::TrustLevel>>> TrustDb::keys(const QString &encryption,
+ const QList<QString> &keyOwnerJids,
+ const QXmpp::TrustLevels trustLevels)
+{
+ QHash<QString, QHash<QByteArray, QXmpp::TrustLevel>> ret;
+ const auto keys = db.keys(encryption);
+
+ for (const auto &jid : keyOwnerJids)
+ for (const auto &k : keys)
+ {
+ if (k.owner != jid)
+ continue;
+
+ QXmpp::TrustLevel level;
+
+ if (toTrustLevel(k.trust_level, level))
+ {
+ std::cerr << "TrustDb::keys: invalid trust level: "
+ << qPrintable(k.trust_level) << std::endl;
+ continue;
+ }
+ else if (!trustLevels.testFlag(level))
+ continue;
+
+ QHash<QByteArray, QXmpp::TrustLevel> h;
+
+ for (const auto &key : k.keys)
+ h.insert(key, level);
+
+ ret.insert(jid, h);
+ break;
+ }
+
+ return QXmpp::Private::makeReadyTask(QHash<QString,
+ QHash<QByteArray, QXmpp::TrustLevel>>(ret));
+}
+
+QXmppTask<bool> TrustDb::hasKey(const QString &encryption,
+ const QString &keyOwnerJid, QXmpp::TrustLevels trustLevels)
+{
+ bool ret = false;
+ const auto keys = db.keys(encryption);
+
+ for (const auto &k : keys)
+ {
+ if (k.owner != keyOwnerJid)
+ continue;
+
+ QXmpp::TrustLevel level;
+
+ if (toTrustLevel(k.trust_level, level))
+ {
+ std::cerr << "TrustDb::keys: invalid trust level: "
+ << qPrintable(k.trust_level) << std::endl;
+ break;
+ }
+ else if (!trustLevels.testFlag(level))
+ break;
+
+ ret = true;
+ break;
+ }
+
+ return QXmpp::Private::makeReadyTask(bool(ret));
+}
+
+QXmppTask<QHash<QString,
+ QMultiHash<QString, QByteArray>>> TrustDb::setTrustLevel(
+ const QString &encryption,
+ const QMultiHash<QString, QByteArray> &keyIds,
+ const QXmpp::TrustLevel trustLevel)
+{
+ QHash<QString, QMultiHash<QString, QByteArray>> ret;
+ auto keys = db.keys(encryption);
+ const auto str = toString(trustLevel);
+
+ for (const auto &owner : keyIds.keys())
+ for (auto &k : keys)
+ {
+ if (k.owner != owner)
+ continue;
+
+ k.trust_level = str;
+ QMultiHash<QString, QByteArray> mh;
+
+ for (const auto &id : k.keys)
+ mh.insert(owner, id);
+
+ ret.insert(owner, mh);
+ break;
+ }
+
+ for (const auto &key : keys)
+ if (db.store(encryption, key))
+ std::cerr << "TrustDb::setTrustLevel: store failed"
+ << std::endl;
+
+ return QXmpp::Private::makeReadyTask(QHash<QString,
+ QMultiHash<QString, QByteArray>>(ret));
+}
+
+QXmppTask<QHash<QString,
+ QMultiHash<QString, QByteArray>>> TrustDb::setTrustLevel(
+ const QString &encryption,
+ const QList<QString> &keyOwnerJids,
+ const QXmpp::TrustLevel oldTrustLevel,
+ const QXmpp::TrustLevel newTrustLevel)
+{
+ QHash<QString, QMultiHash<QString, QByteArray>> ret;
+ auto keys = db.keys(encryption);
+ const auto str = toString(newTrustLevel);
+
+ for (const auto &owner : keyOwnerJids)
+ for (auto &k : keys)
+ {
+ QXmpp::TrustLevel trust_level;
+
+ if (k.owner != owner
+ || toTrustLevel(k.trust_level, trust_level)
+ || trust_level != oldTrustLevel)
+ continue;
+
+ k.trust_level = str;
+ QMultiHash<QString, QByteArray> mh;
+
+ for (const auto &id : k.keys)
+ mh.insert(owner, id);
+
+ ret.insert(owner, mh);
+ break;
+ }
+
+ for (const auto &key : keys)
+ if (db.store(encryption, key))
+ std::cerr << "TrustDb::setTrustLevel: store failed" << std::endl;
+
+ return QXmpp::Private::makeReadyTask(QHash<QString,
+ QMultiHash<QString, QByteArray>>(ret));
+}
+
+QXmppTask<QXmpp::TrustLevel> TrustDb::trustLevel(const QString &encryption,
+ const QString &keyOwnerJid, const QByteArray &keyId)
+{
+ const auto keys = db.keys(encryption);
+
+ for (const auto &k : keys)
+ {
+ if (k.owner != keyOwnerJid)
+ continue;
+
+ for (const auto &id : k.keys)
+ if (id == keyId)
+ {
+ QXmpp::TrustLevel ret(QXmpp::TrustLevel::Undecided);
+
+ if (toTrustLevel(k.trust_level, ret))
+ std::cerr << "toTrustLevel failed" << std::endl;
+
+ return QXmpp::Private::makeReadyTask( QXmpp::TrustLevel(ret));
+ }
+ }
+
+ return QXmpp::Private::makeReadyTask(QXmpp::TrustLevel(
+ QXmpp::TrustLevel::Undecided));
+}
+
+QXmppTask<void> TrustDb::resetAll(const QString &encryption)
+{
+ db.removeKeys(encryption);
+
+ return QXmpp::Private::makeReadyTask();
+}
+
+namespace QXmpp
+{
+
+uint qHash(const TrustLevel &key, uint seed)
+{
+ return qHash(key, seed);
+}
+
+}
diff --git a/trust_db.h b/trust_db.h
new file mode 100644
index 0000000..7d0e58c
--- /dev/null
+++ b/trust_db.h
@@ -0,0 +1,72 @@
+#ifndef TRUST_DB_H
+#define TRUST_DB_H
+
+#include "jiddb.h"
+#include <QXmppClient.h>
+#include <QXmppTask.h>
+#include <QXmppTrustLevel.h>
+#include <QXmppTrustStorage.h>
+#include <QByteArray>
+#include <QHash>
+#include <QList>
+#include <QMultiHash>
+#include <QString>
+
+class TrustDb : virtual public QXmppTrustStorage
+{
+public:
+ TrustDb(const QString &jid, const JidDb &db);
+
+ QXmppTask<void> setSecurityPolicy(const QString &encryption,
+ QXmpp::TrustSecurityPolicy securityPolicy) override;
+ QXmppTask<void> resetSecurityPolicy(const QString &encryption) override;
+ QXmppTask<QXmpp::TrustSecurityPolicy> securityPolicy(
+ const QString &encryption) override;
+
+ QXmppTask<void> setOwnKey(const QString &encryption,
+ const QByteArray &keyId) override;
+ QXmppTask<void> resetOwnKey(const QString &encryption) override;
+ QXmppTask<QByteArray> ownKey(const QString &encryption) override;
+
+ QXmppTask<void> addKeys(const QString &encryption,
+ const QString &keyOwnerJid, const QList<QByteArray> &keyIds,
+ QXmpp::TrustLevel trustLevel
+ = QXmpp::TrustLevel::AutomaticallyDistrusted) override;
+ QXmppTask<void> removeKeys(const QString &encryption,
+ const QList<QByteArray> &keyIds) override;
+ QXmppTask<void> removeKeys(const QString &encryption,
+ const QString &keyOwnerJid) override;
+ QXmppTask<void> removeKeys(const QString &encryption) override;
+ QXmppTask<QHash<QXmpp::TrustLevel,
+ QMultiHash<QString, QByteArray>>> keys(const QString &encryption,
+ QXmpp::TrustLevels trustLevels = {}) override;
+ QXmppTask<QHash<QString,
+ QHash<QByteArray, QXmpp::TrustLevel>>> keys(const QString &encryption,
+ const QList<QString> &keyOwnerJids,
+ QXmpp::TrustLevels trustLevels = {}) override;
+ QXmppTask<bool> hasKey(const QString &encryption,
+ const QString &keyOwnerJid, QXmpp::TrustLevels trustLevels) override;
+
+ QXmppTask<QHash<QString,
+ QMultiHash<QString, QByteArray>>> setTrustLevel(
+ const QString &encryption,
+ const QMultiHash<QString, QByteArray> &keyIds,
+ QXmpp::TrustLevel trustLevel) override;
+ QXmppTask<QHash<QString,
+ QMultiHash<QString, QByteArray>>> setTrustLevel(
+ const QString &encryption,
+ const QList<QString> &keyOwnerJids,
+ QXmpp::TrustLevel oldTrustLevel,
+ QXmpp::TrustLevel newTrustLevel) override;
+ QXmppTask<QXmpp::TrustLevel> trustLevel(const QString &encryption,
+ const QString &keyOwnerJid, const QByteArray &keyId) override;
+
+ QXmppTask<void> resetAll(const QString &encryption) override;
+
+private:
+ const QString &jid;
+ const JidDb &db;
+ QString service() const;
+};
+
+#endif