squawk/qomemo/qomemo.cpp

244 lines
5.9 KiB
C++

/*
* 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);
}
}