forked from blue/squawk
feat(OMEMO): qxmppfactories, refactoring
This commit is contained in:
parent
b22a4c8ca3
commit
6721b62629
@ -3,4 +3,6 @@ target_sources(squawk PRIVATE
|
|||||||
signal.cpp
|
signal.cpp
|
||||||
qomemo.cpp
|
qomemo.cpp
|
||||||
qomemo.h
|
qomemo.h
|
||||||
|
sce.cpp
|
||||||
|
sce.h
|
||||||
)
|
)
|
@ -3,77 +3,16 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "qomemo.h"
|
#include "qomemo.h"
|
||||||
|
#include "sce.h"
|
||||||
|
#include <shared/qxmppfactories.h>
|
||||||
|
|
||||||
|
#include <QBuffer>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QDomDocument>
|
||||||
|
|
||||||
static bool elementMatches(const QXmppElement &element, const QString &tagName,
|
using namespace QXmpp::Factories;
|
||||||
const QString &xmlns = QStringLiteral("")) {
|
|
||||||
if (element.tagName() != tagName) {
|
|
||||||
qWarning() << "tag name: expected = " << tagName
|
|
||||||
<< ", got = " << element.tagName();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!xmlns.isEmpty() && element.attribute("xmlns") != xmlns) {
|
QXmppElement QXmpp::Omemo::DeviceList::toXml() const {
|
||||||
qWarning() << "xmlns: expected = " << xmlns
|
|
||||||
<< ", got = " << element.attribute("xmlns");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static QXmppElement createElement(const QString &tagName,
|
|
||||||
const QString &xmlns = QStringLiteral("")) {
|
|
||||||
QXmppElement el{};
|
|
||||||
el.setTagName(tagName);
|
|
||||||
|
|
||||||
if (!xmlns.isEmpty())
|
|
||||||
el.setAttribute("xmlns", xmlns);
|
|
||||||
|
|
||||||
return el;
|
|
||||||
}
|
|
||||||
|
|
||||||
static QXmppElement createValue(const QString &value) {
|
|
||||||
auto el = createElement("value");
|
|
||||||
el.setValue(value);
|
|
||||||
|
|
||||||
return el;
|
|
||||||
}
|
|
||||||
|
|
||||||
static QXmppElement createField(const QString &key, const QString &value,
|
|
||||||
bool hidden = false) {
|
|
||||||
auto field = createElement("field");
|
|
||||||
field.setAttribute("var", key);
|
|
||||||
if (hidden)
|
|
||||||
field.setAttribute("type", "hidden");
|
|
||||||
field.appendChild(createValue(value));
|
|
||||||
|
|
||||||
return field;
|
|
||||||
}
|
|
||||||
|
|
||||||
static QXmppElement
|
|
||||||
createOpenPublishOptions(const QString &maxItems = QStringLiteral("")) {
|
|
||||||
auto formType = createField(
|
|
||||||
"FORM_TYPE", "http://jabber.org/protocol/pubsub#publish-options", true);
|
|
||||||
auto accessModel = createField("pubsub#access_model", "open");
|
|
||||||
|
|
||||||
auto x = createElement("x", "jabber:x:data");
|
|
||||||
x.setAttribute("type", "submit");
|
|
||||||
|
|
||||||
x.appendChild(formType);
|
|
||||||
x.appendChild(accessModel);
|
|
||||||
|
|
||||||
if (!maxItems.isEmpty())
|
|
||||||
x.appendChild(createField("pubsub#max_items", maxItems));
|
|
||||||
|
|
||||||
auto publishOptions = createElement("publish-options");
|
|
||||||
publishOptions.appendChild(x);
|
|
||||||
|
|
||||||
return publishOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
QXmppElement QOmemo::DeviceList::toXml() const {
|
|
||||||
auto element = createElement("devices", "urn:xmpp:omemo:1");
|
auto element = createElement("devices", "urn:xmpp:omemo:1");
|
||||||
|
|
||||||
for (const auto &d : devices) {
|
for (const auto &d : devices) {
|
||||||
@ -83,7 +22,7 @@ QXmppElement QOmemo::DeviceList::toXml() const {
|
|||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
QXmppIq QOmemo::DeviceList::toIq() const {
|
QXmppIq QXmpp::Omemo::DeviceList::toIq() const {
|
||||||
QXmppIq iq{};
|
QXmppIq iq{};
|
||||||
|
|
||||||
iq.setType(QXmppIq::Set);
|
iq.setType(QXmppIq::Set);
|
||||||
@ -105,7 +44,7 @@ QXmppIq QOmemo::DeviceList::toIq() const {
|
|||||||
return iq;
|
return iq;
|
||||||
}
|
}
|
||||||
|
|
||||||
void QOmemo::DeviceList::fromXml(const QXmppElement &element) {
|
void QXmpp::Omemo::DeviceList::fromXml(const QXmppElement &element) {
|
||||||
if (!elementMatches(element, "devices", "urn:xmpp:omemo:1"))
|
if (!elementMatches(element, "devices", "urn:xmpp:omemo:1"))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -121,7 +60,7 @@ void QOmemo::DeviceList::fromXml(const QXmppElement &element) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QXmppElement QOmemo::Device::toXml() const {
|
QXmppElement QXmpp::Omemo::Device::toXml() const {
|
||||||
auto result = createElement("device");
|
auto result = createElement("device");
|
||||||
result.setAttribute("id", QString::number(id));
|
result.setAttribute("id", QString::number(id));
|
||||||
|
|
||||||
@ -132,7 +71,7 @@ QXmppElement QOmemo::Device::toXml() const {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void QOmemo::Device::fromXml(const QXmppElement &element) {
|
void QXmpp::Omemo::Device::fromXml(const QXmppElement &element) {
|
||||||
if (!elementMatches(element, "device"))
|
if (!elementMatches(element, "device"))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -140,7 +79,7 @@ void QOmemo::Device::fromXml(const QXmppElement &element) {
|
|||||||
label = element.attribute("label");
|
label = element.attribute("label");
|
||||||
}
|
}
|
||||||
|
|
||||||
QXmppElement QOmemo::PreKey::toXml() const {
|
QXmppElement QXmpp::Omemo::PreKey::toXml() const {
|
||||||
auto pk = createElement("pk");
|
auto pk = createElement("pk");
|
||||||
pk.setAttribute("id", QString::number(id));
|
pk.setAttribute("id", QString::number(id));
|
||||||
// TODO: Base64
|
// TODO: Base64
|
||||||
@ -149,7 +88,7 @@ QXmppElement QOmemo::PreKey::toXml() const {
|
|||||||
return pk;
|
return pk;
|
||||||
}
|
}
|
||||||
|
|
||||||
void QOmemo::PreKey::fromXml(const QXmppElement &element) {
|
void QXmpp::Omemo::PreKey::fromXml(const QXmppElement &element) {
|
||||||
if (!elementMatches(element, "pk"))
|
if (!elementMatches(element, "pk"))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -158,7 +97,7 @@ void QOmemo::PreKey::fromXml(const QXmppElement &element) {
|
|||||||
data = element.value();
|
data = element.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
QXmppElement QOmemo::Bundle::toXml() const {
|
QXmppElement QXmpp::Omemo::Bundle::toXml() const {
|
||||||
auto spkNode = createElement("spk");
|
auto spkNode = createElement("spk");
|
||||||
spkNode.setAttribute("id", QString::number(spkId));
|
spkNode.setAttribute("id", QString::number(spkId));
|
||||||
spkNode.setValue(spk);
|
spkNode.setValue(spk);
|
||||||
@ -183,7 +122,7 @@ QXmppElement QOmemo::Bundle::toXml() const {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
QXmppIq QOmemo::Bundle::toIq() const {
|
QXmppIq QXmpp::Omemo::Bundle::toIq() const {
|
||||||
QXmppIq iq{};
|
QXmppIq iq{};
|
||||||
|
|
||||||
iq.setType(QXmppIq::Set);
|
iq.setType(QXmppIq::Set);
|
||||||
@ -205,7 +144,7 @@ QXmppIq QOmemo::Bundle::toIq() const {
|
|||||||
return iq;
|
return iq;
|
||||||
}
|
}
|
||||||
|
|
||||||
void QOmemo::Bundle::fromXml(const QXmppElement &element) {
|
void QXmpp::Omemo::Bundle::fromXml(const QXmppElement &element) {
|
||||||
if (!elementMatches(element, "bundle", "urn:xmpp:omemo:1"))
|
if (!elementMatches(element, "bundle", "urn:xmpp:omemo:1"))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -241,3 +180,101 @@ void QOmemo::Bundle::fromXml(const QXmppElement &element) {
|
|||||||
prekeys.push_back(pk);
|
prekeys.push_back(pk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QXmppPubSubIq QXmpp::Omemo::Bundle::fetchDeviceBundleIq(int deviceId) {
|
||||||
|
QXmppPubSubIq iq{};
|
||||||
|
iq.setType(QXmppIq::Get);
|
||||||
|
iq.setQueryNode("urn:xmpp:omemo:1:bundles");
|
||||||
|
|
||||||
|
QXmppPubSubItem item{};
|
||||||
|
item.setId(QString::number(deviceId));
|
||||||
|
iq.setItems({item});
|
||||||
|
|
||||||
|
return iq;
|
||||||
|
}
|
||||||
|
|
||||||
|
QXmppElement QXmpp::Omemo::EncryptedMessage::header() const {
|
||||||
|
auto result = createElement("header");
|
||||||
|
result.setAttribute("sid", QString::number(fromDeviceId));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QXmppElement QXmpp::Omemo::EncryptedMessage::toXml() const {
|
||||||
|
auto result = createElement("encrypted", "urn:xmpp:omemo:1");
|
||||||
|
|
||||||
|
result.appendChild(header());
|
||||||
|
|
||||||
|
for (const auto &key : keys) {
|
||||||
|
result.appendChild(key.toXml());
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QXmppElement QXmpp::Omemo::EncryptedMessage::payload() const {
|
||||||
|
QBuffer buffer;
|
||||||
|
buffer.open(QIODevice::ReadWrite);
|
||||||
|
QXmlStreamWriter writer(&buffer);
|
||||||
|
message.toXml(&writer);
|
||||||
|
|
||||||
|
QDomDocument doc;
|
||||||
|
doc.setContent(buffer.data(), true);
|
||||||
|
|
||||||
|
QXmppElement root(doc.documentElement());
|
||||||
|
root.setTagName("payload");
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
QXmppElement QXmpp::Omemo::EncryptedMessage::content() const {
|
||||||
|
auto envelope = createElement("content", "urn:xmpp:sce:0");
|
||||||
|
|
||||||
|
envelope.appendChild(payload());
|
||||||
|
|
||||||
|
if (!from.isEmpty()) {
|
||||||
|
auto fromNode = createElement("from");
|
||||||
|
fromNode.setAttribute("jid", from);
|
||||||
|
envelope.appendChild(fromNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!to.isEmpty()) {
|
||||||
|
auto toNode = createElement("to");
|
||||||
|
toNode.setAttribute("jid", to);
|
||||||
|
envelope.appendChild(toNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!timestamp.isNull()) {
|
||||||
|
auto timeNode = createElement("time");
|
||||||
|
timeNode.setAttribute("stamp", timestamp.toString(Qt::DateFormat::ISODate));
|
||||||
|
envelope.appendChild(timeNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto rpad = createElement("rpad");
|
||||||
|
rpad.setValue(QXmpp::Sce::generatePadding());
|
||||||
|
envelope.appendChild(rpad);
|
||||||
|
|
||||||
|
return envelope;
|
||||||
|
}
|
||||||
|
|
||||||
|
QXmppElement QXmpp::Omemo::MessageKey::toXml() const {
|
||||||
|
auto result = createElement("key");
|
||||||
|
|
||||||
|
if (kex)
|
||||||
|
result.setAttribute("kex", "true");
|
||||||
|
|
||||||
|
result.setValue(key);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QXmppElement QXmpp::Omemo::HeaderKeys::toXml() const {
|
||||||
|
auto result = createElement("keys");
|
||||||
|
result.setAttribute("jid", jid);
|
||||||
|
|
||||||
|
for (const auto &key : keys) {
|
||||||
|
result.appendChild(key.toXml());
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
@ -6,7 +6,10 @@
|
|||||||
|
|
||||||
#include <QXmppPubSubIq.h>
|
#include <QXmppPubSubIq.h>
|
||||||
|
|
||||||
namespace QOmemo {
|
#include <QDateTime>
|
||||||
|
#include <QXmppMessage.h>
|
||||||
|
|
||||||
|
namespace QXmpp::Omemo {
|
||||||
|
|
||||||
class Device {
|
class Device {
|
||||||
public:
|
public:
|
||||||
@ -40,6 +43,8 @@ public:
|
|||||||
|
|
||||||
class Bundle {
|
class Bundle {
|
||||||
public:
|
public:
|
||||||
|
[[nodiscard]] static QXmppPubSubIq fetchDeviceBundleIq(int deviceId);
|
||||||
|
|
||||||
[[nodiscard]] QXmppElement toXml() const;
|
[[nodiscard]] QXmppElement toXml() const;
|
||||||
[[nodiscard]] QXmppIq toIq() const;
|
[[nodiscard]] QXmppIq toIq() const;
|
||||||
void fromXml(const QXmppElement &element);
|
void fromXml(const QXmppElement &element);
|
||||||
@ -53,4 +58,37 @@ public:
|
|||||||
QList<PreKey> prekeys;
|
QList<PreKey> prekeys;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class MessageKey {
|
||||||
|
public:
|
||||||
|
[[nodiscard]] QXmppElement toXml() const;
|
||||||
|
|
||||||
|
bool kex{};
|
||||||
|
QString key{};
|
||||||
|
};
|
||||||
|
|
||||||
|
class HeaderKeys {
|
||||||
|
public:
|
||||||
|
[[nodiscard]] QXmppElement toXml() const;
|
||||||
|
|
||||||
|
QString jid{};
|
||||||
|
QList<MessageKey> keys{};
|
||||||
|
};
|
||||||
|
|
||||||
|
class EncryptedMessage {
|
||||||
|
public:
|
||||||
|
[[nodiscard]] QXmppElement header() const;
|
||||||
|
[[nodiscard]] QXmppElement content() const;
|
||||||
|
[[nodiscard]] QXmppElement toXml() const;
|
||||||
|
[[nodiscard]] QXmppElement payload() const;
|
||||||
|
|
||||||
|
int fromDeviceId{};
|
||||||
|
|
||||||
|
QList<HeaderKeys> keys{};
|
||||||
|
QString from{};
|
||||||
|
QString to{};
|
||||||
|
QDateTime timestamp{};
|
||||||
|
|
||||||
|
QXmppMessage message{};
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace QOmemo
|
} // namespace QOmemo
|
||||||
|
24
qomemo/sce.cpp
Normal file
24
qomemo/sce.cpp
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Created by victoria on 2021-05-12.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "sce.h"
|
||||||
|
|
||||||
|
#include <QRandomGenerator>
|
||||||
|
|
||||||
|
constexpr int RPAD_MAX_LENGTH = 200;
|
||||||
|
|
||||||
|
QString QXmpp::Sce::generatePadding() {
|
||||||
|
QRandomGenerator random{};
|
||||||
|
QString result{};
|
||||||
|
QString alphabet{ QStringLiteral("!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~") };
|
||||||
|
|
||||||
|
auto length = random.bounded(RPAD_MAX_LENGTH);
|
||||||
|
result.resize(length);
|
||||||
|
|
||||||
|
for (auto i = 0; i < length; ++i) {
|
||||||
|
result[i] = alphabet[random.bounded(alphabet.length())];
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
14
qomemo/sce.h
Normal file
14
qomemo/sce.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* Created by victoria on 2021-05-12.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QXmppElement.h>
|
||||||
|
#include <QDateTime>
|
||||||
|
|
||||||
|
namespace QXmpp::Sce {
|
||||||
|
|
||||||
|
QString generatePadding();
|
||||||
|
|
||||||
|
}
|
@ -16,4 +16,6 @@ target_sources(squawk PRIVATE
|
|||||||
utils.h
|
utils.h
|
||||||
vcard.cpp
|
vcard.cpp
|
||||||
vcard.h
|
vcard.h
|
||||||
|
qxmppfactories.cpp
|
||||||
|
qxmppfactories.h
|
||||||
)
|
)
|
||||||
|
75
shared/qxmppfactories.cpp
Normal file
75
shared/qxmppfactories.cpp
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* Created by victoria on 2021-05-12.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "qxmppfactories.h"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
bool QXmpp::Factories::elementMatches(const QXmppElement &element,
|
||||||
|
const QString &tagName,
|
||||||
|
const QString &xmlns) {
|
||||||
|
if (element.tagName() != tagName) {
|
||||||
|
qWarning() << "tag name: expected = " << tagName
|
||||||
|
<< ", got = " << element.tagName();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!xmlns.isEmpty() && element.attribute("xmlns") != xmlns) {
|
||||||
|
qWarning() << "xmlns: expected = " << xmlns
|
||||||
|
<< ", got = " << element.attribute("xmlns");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QXmppElement QXmpp::Factories::createElement(const QString &tagName,
|
||||||
|
const QString &xmlns) {
|
||||||
|
QXmppElement el{};
|
||||||
|
el.setTagName(tagName);
|
||||||
|
|
||||||
|
if (!xmlns.isEmpty())
|
||||||
|
el.setAttribute("xmlns", xmlns);
|
||||||
|
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
|
||||||
|
QXmppElement QXmpp::Factories::createValue(const QString &value) {
|
||||||
|
auto el = createElement("value");
|
||||||
|
el.setValue(value);
|
||||||
|
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
|
||||||
|
QXmppElement QXmpp::Factories::createField(const QString &key,
|
||||||
|
const QString &value, bool hidden) {
|
||||||
|
auto field = createElement("field");
|
||||||
|
field.setAttribute("var", key);
|
||||||
|
if (hidden)
|
||||||
|
field.setAttribute("type", "hidden");
|
||||||
|
field.appendChild(createValue(value));
|
||||||
|
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
|
||||||
|
QXmppElement
|
||||||
|
QXmpp::Factories::createOpenPublishOptions(const QString &maxItems) {
|
||||||
|
auto formType = createField(
|
||||||
|
"FORM_TYPE", "http://jabber.org/protocol/pubsub#publish-options", true);
|
||||||
|
auto accessModel = createField("pubsub#access_model", "open");
|
||||||
|
|
||||||
|
auto x = createElement("x", "jabber:x:data");
|
||||||
|
x.setAttribute("type", "submit");
|
||||||
|
|
||||||
|
x.appendChild(formType);
|
||||||
|
x.appendChild(accessModel);
|
||||||
|
|
||||||
|
if (!maxItems.isEmpty())
|
||||||
|
x.appendChild(createField("pubsub#max_items", maxItems));
|
||||||
|
|
||||||
|
auto publishOptions = createElement("publish-options");
|
||||||
|
publishOptions.appendChild(x);
|
||||||
|
|
||||||
|
return publishOptions;
|
||||||
|
}
|
25
shared/qxmppfactories.h
Normal file
25
shared/qxmppfactories.h
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Created by victoria on 2021-05-12.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QXmppElement.h>
|
||||||
|
|
||||||
|
namespace QXmpp::Factories {
|
||||||
|
|
||||||
|
bool elementMatches(const QXmppElement &element, const QString &tagName,
|
||||||
|
const QString &xmlns = QStringLiteral(""));
|
||||||
|
|
||||||
|
QXmppElement createElement(const QString &tagName,
|
||||||
|
const QString &xmlns = QStringLiteral(""));
|
||||||
|
|
||||||
|
QXmppElement createValue(const QString &value);
|
||||||
|
|
||||||
|
QXmppElement createField(const QString &key, const QString &value,
|
||||||
|
bool hidden = false);
|
||||||
|
|
||||||
|
QXmppElement
|
||||||
|
createOpenPublishOptions(const QString &maxItems = QStringLiteral(""));
|
||||||
|
|
||||||
|
} // namespace QXmpp::Factories
|
Loading…
Reference in New Issue
Block a user