forked from blue/squawk
feat(OMEMO): add Device, DeviceList, PreKey, Bundle + XML
This commit is contained in:
parent
a6254d88b3
commit
b22a4c8ca3
@ -1 +1,6 @@
|
|||||||
target_sources(squawk PRIVATE signal.h signal.cpp)
|
target_sources(squawk PRIVATE
|
||||||
|
signal.h
|
||||||
|
signal.cpp
|
||||||
|
qomemo.cpp
|
||||||
|
qomemo.h
|
||||||
|
)
|
243
qomemo/qomemo.cpp
Normal file
243
qomemo/qomemo.cpp
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
/*
|
||||||
|
* Created by victoria on 2021-05-12.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "qomemo.h"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
static bool elementMatches(const QXmppElement &element, const QString &tagName,
|
||||||
|
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) {
|
||||||
|
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");
|
||||||
|
|
||||||
|
for (const auto &d : devices) {
|
||||||
|
element.appendChild(d.toXml());
|
||||||
|
}
|
||||||
|
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
QXmppIq QOmemo::DeviceList::toIq() const {
|
||||||
|
QXmppIq iq{};
|
||||||
|
|
||||||
|
iq.setType(QXmppIq::Set);
|
||||||
|
|
||||||
|
auto item = createElement("item");
|
||||||
|
item.setAttribute("id", "current");
|
||||||
|
item.appendChild(toXml());
|
||||||
|
|
||||||
|
auto publish = createElement("publish");
|
||||||
|
publish.setAttribute("node", "urn:xmpp:omemo:1:devices");
|
||||||
|
publish.appendChild(item);
|
||||||
|
|
||||||
|
auto pubSub = createElement("pubsub", "http://jabber.org/protocol/pubsub");
|
||||||
|
pubSub.appendChild(publish);
|
||||||
|
pubSub.appendChild(createOpenPublishOptions());
|
||||||
|
|
||||||
|
iq.extensions().push_back(pubSub);
|
||||||
|
|
||||||
|
return iq;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QOmemo::DeviceList::fromXml(const QXmppElement &element) {
|
||||||
|
if (!elementMatches(element, "devices", "urn:xmpp:omemo:1"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
devices.clear();
|
||||||
|
|
||||||
|
auto deviceElement = element.firstChildElement("device");
|
||||||
|
while (!deviceElement.isNull()) {
|
||||||
|
Device device{};
|
||||||
|
device.fromXml(deviceElement);
|
||||||
|
devices.push_back(device);
|
||||||
|
|
||||||
|
deviceElement = deviceElement.nextSiblingElement("device");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QXmppElement QOmemo::Device::toXml() const {
|
||||||
|
auto result = createElement("device");
|
||||||
|
result.setAttribute("id", QString::number(id));
|
||||||
|
|
||||||
|
if (!label.isEmpty()) {
|
||||||
|
result.setAttribute("label", label);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QOmemo::Device::fromXml(const QXmppElement &element) {
|
||||||
|
if (!elementMatches(element, "device"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
id = element.attribute("id").toInt();
|
||||||
|
label = element.attribute("label");
|
||||||
|
}
|
||||||
|
|
||||||
|
QXmppElement QOmemo::PreKey::toXml() const {
|
||||||
|
auto pk = createElement("pk");
|
||||||
|
pk.setAttribute("id", QString::number(id));
|
||||||
|
// TODO: Base64
|
||||||
|
pk.setValue(data);
|
||||||
|
|
||||||
|
return pk;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QOmemo::PreKey::fromXml(const QXmppElement &element) {
|
||||||
|
if (!elementMatches(element, "pk"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
id = element.attribute("id").toInt();
|
||||||
|
// TODO: Base64
|
||||||
|
data = element.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
QXmppElement QOmemo::Bundle::toXml() const {
|
||||||
|
auto spkNode = createElement("spk");
|
||||||
|
spkNode.setAttribute("id", QString::number(spkId));
|
||||||
|
spkNode.setValue(spk);
|
||||||
|
|
||||||
|
auto spksNode = createElement("spks");
|
||||||
|
spksNode.setValue(spks);
|
||||||
|
|
||||||
|
auto ikNode = createElement("ik");
|
||||||
|
ikNode.setValue(ik);
|
||||||
|
|
||||||
|
auto prekeysNode = createElement("prekeys");
|
||||||
|
for (const auto &pk : prekeys) {
|
||||||
|
prekeysNode.appendChild(pk.toXml());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = createElement("bundle", "urn:xmpp:omemo:1");
|
||||||
|
result.appendChild(spkNode);
|
||||||
|
result.appendChild(spksNode);
|
||||||
|
result.appendChild(ikNode);
|
||||||
|
result.appendChild(prekeysNode);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QXmppIq QOmemo::Bundle::toIq() const {
|
||||||
|
QXmppIq iq{};
|
||||||
|
|
||||||
|
iq.setType(QXmppIq::Set);
|
||||||
|
|
||||||
|
auto item = createElement("item");
|
||||||
|
item.setAttribute("id", QString::number(deviceId));
|
||||||
|
item.appendChild(toXml());
|
||||||
|
|
||||||
|
auto publish = createElement("publish");
|
||||||
|
publish.setAttribute("node", "urn:xmpp:omemo:1:bundles");
|
||||||
|
publish.appendChild(item);
|
||||||
|
|
||||||
|
auto pubSub = createElement("pubsub", "http://jabber.org/protocol/pubsub");
|
||||||
|
pubSub.appendChild(publish);
|
||||||
|
pubSub.appendChild(createOpenPublishOptions("max"));
|
||||||
|
|
||||||
|
iq.extensions().push_back(pubSub);
|
||||||
|
|
||||||
|
return iq;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QOmemo::Bundle::fromXml(const QXmppElement &element) {
|
||||||
|
if (!elementMatches(element, "bundle", "urn:xmpp:omemo:1"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto spkNode = element.firstChildElement("spk");
|
||||||
|
if (spkNode.isNull()) {
|
||||||
|
qWarning() << "'bundle': missing 'spk'";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
spk = spkNode.value();
|
||||||
|
spkId = spkNode.attribute("id").toInt();
|
||||||
|
|
||||||
|
auto spksNode = element.firstChildElement("spks");
|
||||||
|
if (spksNode.isNull()) {
|
||||||
|
qWarning() << "'bundle': missing 'spks'";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
spks = spksNode.value();
|
||||||
|
|
||||||
|
auto ikNode = element.firstChildElement("ik");
|
||||||
|
if (ikNode.isNull()) {
|
||||||
|
qWarning() << "'bundle': missing 'ik'";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ik = ikNode.value();
|
||||||
|
|
||||||
|
auto prekeysNode = element.firstChildElement("prekeys");
|
||||||
|
auto pkNode = prekeysNode.firstChildElement("pk");
|
||||||
|
|
||||||
|
prekeys.clear();
|
||||||
|
while (!pkNode.isNull()) {
|
||||||
|
PreKey pk{};
|
||||||
|
pk.fromXml(pkNode);
|
||||||
|
prekeys.push_back(pk);
|
||||||
|
}
|
||||||
|
}
|
56
qomemo/qomemo.h
Normal file
56
qomemo/qomemo.h
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* Created by victoria on 2021-05-12.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QXmppPubSubIq.h>
|
||||||
|
|
||||||
|
namespace QOmemo {
|
||||||
|
|
||||||
|
class Device {
|
||||||
|
public:
|
||||||
|
[[nodiscard]] QXmppElement toXml() const;
|
||||||
|
void fromXml(const QXmppElement &element);
|
||||||
|
|
||||||
|
int id;
|
||||||
|
QString label;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DeviceList {
|
||||||
|
public:
|
||||||
|
[[nodiscard]] QXmppElement toXml() const;
|
||||||
|
[[nodiscard]] QXmppIq toIq() const;
|
||||||
|
/// Expects a urn:xmpp:omemo:1:devices node
|
||||||
|
void fromXml(const QXmppElement &element);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QList<Device> devices;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PreKey {
|
||||||
|
public:
|
||||||
|
[[nodiscard]] QXmppElement toXml() const;
|
||||||
|
/// Expects a <pk>
|
||||||
|
void fromXml(const QXmppElement &element);
|
||||||
|
|
||||||
|
int id;
|
||||||
|
QString data;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Bundle {
|
||||||
|
public:
|
||||||
|
[[nodiscard]] QXmppElement toXml() const;
|
||||||
|
[[nodiscard]] QXmppIq toIq() const;
|
||||||
|
void fromXml(const QXmppElement &element);
|
||||||
|
|
||||||
|
int deviceId;
|
||||||
|
|
||||||
|
QString spk;
|
||||||
|
int spkId;
|
||||||
|
QString spks;
|
||||||
|
QString ik;
|
||||||
|
QList<PreKey> prekeys;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QOmemo
|
Loading…
Reference in New Issue
Block a user