From 9fbbe0c120f69acbf62d9a5adc0e052cba9d8021 Mon Sep 17 00:00:00 2001
From: vae <vae@programming.socks.town>
Date: Mon, 10 May 2021 22:42:17 +0300
Subject: [PATCH 01/20] build: add signal-protocol-c dependency

---
 .gitmodules                |  3 +++
 CMakeLists.txt             | 10 +++++++++-
 external/signal-protocol-c |  1 +
 3 files changed, 13 insertions(+), 1 deletion(-)
 create mode 160000 external/signal-protocol-c

diff --git a/.gitmodules b/.gitmodules
index bbe5364..c205907 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,6 @@
 [submodule "external/qxmpp"]
 	path = external/qxmpp
 	url = https://github.com/qxmpp-project/qxmpp.git
+[submodule "external/signal-protocol-c"]
+	path = external/signal-protocol-c
+	url = https://github.com/signalapp/libsignal-protocol-c.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e88fdc8..7193a6f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -64,7 +64,8 @@ add_custom_target(translations ALL DEPENDS ${QM_FILES})
 
 qt5_add_resources(RCC resources/resources.qrc)
 
-option(SYSTEM_QXMPP "Use system qxmpp lib" ON) 
+option(SYSTEM_QXMPP "Use system qxmpp lib" ON)
+option(SYSTEM_SIGNAL "Use system signal-protocol-c lib" OFF)
 option(WITH_KWALLET "Build KWallet support module" ON) 
 option(WITH_KIO "Build KIO support module" ON) 
 
@@ -79,10 +80,16 @@ if (SYSTEM_QXMPP)
     endif()
 endif()
 
+# TODO: detect if libsignal-protocol-c exists and set SYSTEM_SIGNAL?
+
 if(NOT SYSTEM_QXMPP)
     add_subdirectory(external/qxmpp)
 endif()
 
+if (NOT SYSTEM_SIGNAL)
+    add_subdirectory(external/signal-protocol-c)
+endif()
+
 if (WITH_KWALLET)
     find_package(KF5Wallet CONFIG)
     
@@ -119,6 +126,7 @@ add_subdirectory(external/simpleCrypt)
 target_link_libraries(squawk squawkUI)
 target_link_libraries(squawk squawkCORE)
 target_link_libraries(squawk uuid)
+target_link_libraries(squawk signal-protocol-c)
 
 
 
diff --git a/external/signal-protocol-c b/external/signal-protocol-c
new file mode 160000
index 0000000..3a83a4f
--- /dev/null
+++ b/external/signal-protocol-c
@@ -0,0 +1 @@
+Subproject commit 3a83a4f4ed2302ff6e68ab569c88793b50c22d28

From 04e745fad42711ccdc74e818a3e9e710766148f6 Mon Sep 17 00:00:00 2001
From: vae <vae@programming.socks.town>
Date: Tue, 11 May 2021 00:12:33 +0300
Subject: [PATCH 02/20] build: add qomemo dir with TODO

---
 CMakeLists.txt        |  1 +
 qomemo/CMakeLists.txt | 23 +++++++++++++++++++++++
 qomemo/TODO           | 12 ++++++++++++
 3 files changed, 36 insertions(+)
 create mode 100644 qomemo/CMakeLists.txt
 create mode 100644 qomemo/TODO

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7193a6f..930ae45 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -120,6 +120,7 @@ endif()
 add_subdirectory(ui)
 add_subdirectory(core)
 add_subdirectory(plugins)
+add_subdirectory(qomemo)
 
 add_subdirectory(external/simpleCrypt)
 
diff --git a/qomemo/CMakeLists.txt b/qomemo/CMakeLists.txt
new file mode 100644
index 0000000..5f92df4
--- /dev/null
+++ b/qomemo/CMakeLists.txt
@@ -0,0 +1,23 @@
+cmake_minimum_required(VERSION 3.3)
+project(qomemo)
+
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
+set(CMAKE_AUTOMOC ON)
+
+find_package(Qt5Core CONFIG REQUIRED)
+find_package(Qt5Xml CONFIG REQUIRED)
+find_package(LMDB REQUIRED)
+
+set(qomemo_SRC)
+
+add_library(qomemo STATIC ${qomemo_SRC})
+
+if(SYSTEM_QXMPP)
+    get_target_property(QXMPP_INTERFACE_INCLUDE_DIRECTORIES QXmpp::QXmpp INTERFACE_INCLUDE_DIRECTORIES)
+    target_include_directories(qomemo PUBLIC ${QXMPP_INTERFACE_INCLUDE_DIRECTORIES})
+endif()
+
+target_link_libraries(qomemo Qt5::Core)
+target_link_libraries(qomemo Qt5::Xml)
+target_link_libraries(qomemo qxmpp)
+target_link_libraries(qomemo lmdb)
diff --git a/qomemo/TODO b/qomemo/TODO
new file mode 100644
index 0000000..6a5709d
--- /dev/null
+++ b/qomemo/TODO
@@ -0,0 +1,12 @@
+* Generate device w/ keys
+* PubSub set urn:xmpp:omemo:1:devices to announce new device
+* PubSub set urn:xmpp:omemo:1:bundles to announce new key bundles
+* PubSub get urn:xmpp:omemo:1:bundles to get user bundles
+
+Sending a message:
+* Create urn:xmpp:sce:0 with padding and content
+* Add <encrypted xmlns='urn:xmpp:omemo:1'> with header (key list) and payload (b64)
+
+Receiving a message:
+* Check <keys> => <key>
+* Decrypt (TODO)
\ No newline at end of file

From 7c1ae4737e4461583f52f29a6cbeca7b5e3669aa Mon Sep 17 00:00:00 2001
From: vae <vae@programming.socks.town>
Date: Tue, 11 May 2021 00:32:44 +0300
Subject: [PATCH 03/20] feat(omemo): WIP signal-protocol-c cpp wrapper

---
 qomemo/CMakeLists.txt |  4 +++-
 qomemo/signal.h       | 20 ++++++++++++++++++++
 2 files changed, 23 insertions(+), 1 deletion(-)
 create mode 100644 qomemo/signal.h

diff --git a/qomemo/CMakeLists.txt b/qomemo/CMakeLists.txt
index 5f92df4..0837eac 100644
--- a/qomemo/CMakeLists.txt
+++ b/qomemo/CMakeLists.txt
@@ -8,7 +8,7 @@ find_package(Qt5Core CONFIG REQUIRED)
 find_package(Qt5Xml CONFIG REQUIRED)
 find_package(LMDB REQUIRED)
 
-set(qomemo_SRC)
+set(qomemo_SRC signal.h)
 
 add_library(qomemo STATIC ${qomemo_SRC})
 
@@ -17,6 +17,8 @@ if(SYSTEM_QXMPP)
     target_include_directories(qomemo PUBLIC ${QXMPP_INTERFACE_INCLUDE_DIRECTORIES})
 endif()
 
+target_include_directories(qomemo PUBLIC ${CMAKE_SOURCE_DIR}/external/signal-protocol-c/src)
+
 target_link_libraries(qomemo Qt5::Core)
 target_link_libraries(qomemo Qt5::Xml)
 target_link_libraries(qomemo qxmpp)
diff --git a/qomemo/signal.h b/qomemo/signal.h
new file mode 100644
index 0000000..e7e4c8b
--- /dev/null
+++ b/qomemo/signal.h
@@ -0,0 +1,20 @@
+/*
+ * Created by victoria on 2021-05-11.
+ */
+
+#pragma once
+
+#include <signal_protocol.h>
+
+namespace Signal
+{
+
+class Context {};
+class RatchetIdentityPair {};
+class SessionSignedPreKey {};
+class ProtocolKeyHelper {};
+class ProtocolStoreContext {};
+class SessionBuilder {};
+class SessionCipher {};
+
+}

From 7d648ab081c54563a49e0e6001046c0e629b62ef Mon Sep 17 00:00:00 2001
From: vae <vae@programming.socks.town>
Date: Tue, 11 May 2021 19:11:02 +0300
Subject: [PATCH 04/20] feat: add omemo buttons

---
 ui/CMakeLists.txt          |  2 ++
 ui/squawk.ui               | 20 +++++++++++++++++++-
 ui/widgets/conversation.ui | 17 +++++++++++++++--
 3 files changed, 36 insertions(+), 3 deletions(-)

diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt
index 11b8f3d..2ff67e6 100644
--- a/ui/CMakeLists.txt
+++ b/ui/CMakeLists.txt
@@ -18,6 +18,8 @@ add_subdirectory(widgets)
 
 set(squawkUI_SRC
   squawk.cpp
+  squawk.h
+  squawk.ui
   models/accounts.cpp
   models/roster.cpp
   models/item.cpp
diff --git a/ui/squawk.ui b/ui/squawk.ui
index f6cb300..cb48de6 100644
--- a/ui/squawk.ui
+++ b/ui/squawk.ui
@@ -162,14 +162,24 @@
      <x>0</x>
      <y>0</y>
      <width>718</width>
-     <height>27</height>
+     <height>23</height>
     </rect>
    </property>
    <widget class="QMenu" name="menuSettings">
     <property name="title">
      <string>Settings</string>
     </property>
+    <widget class="QMenu" name="menuOMEMO">
+     <property name="title">
+      <string>OMEMO</string>
+     </property>
+     <property name="icon">
+      <iconset theme="security-high"/>
+     </property>
+     <addaction name="actionDeviceList"/>
+    </widget>
     <addaction name="actionAccounts"/>
+    <addaction name="menuOMEMO"/>
    </widget>
    <widget class="QMenu" name="menuFile">
     <property name="title">
@@ -224,6 +234,14 @@
     <string>Add conference</string>
    </property>
   </action>
+  <action name="actionDeviceList">
+   <property name="icon">
+    <iconset theme="computer-laptop"/>
+   </property>
+   <property name="text">
+    <string>Device list</string>
+   </property>
+  </action>
  </widget>
  <resources/>
  <connections/>
diff --git a/ui/widgets/conversation.ui b/ui/widgets/conversation.ui
index bb38666..36b74a4 100644
--- a/ui/widgets/conversation.ui
+++ b/ui/widgets/conversation.ui
@@ -292,6 +292,19 @@
            </property>
           </spacer>
          </item>
+         <item>
+          <widget class="QPushButton" name="encryptionButton">
+           <property name="text">
+            <string/>
+           </property>
+           <property name="icon">
+            <iconset theme="security-low"/>
+           </property>
+           <property name="flat">
+            <bool>true</bool>
+           </property>
+          </widget>
+         </item>
          <item>
           <widget class="QPushButton" name="attachButton">
            <property name="text">
@@ -400,8 +413,8 @@ background-color: transparent
          <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
 &lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
 p, li { white-space: pre-wrap; }
-&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Liberation Sans'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
-&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Noto Sans'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Liberation Sans';&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
         </property>
         <property name="acceptRichText">
          <bool>false</bool>

From bc66ab7e52dfc49dd2a1ad2472eecdf86a4d8b41 Mon Sep 17 00:00:00 2001
From: vae <vae@programming.socks.town>
Date: Tue, 11 May 2021 23:17:57 +0300
Subject: [PATCH 05/20] build: signal-protocol-c

---
 CMakeLists.txt        |  9 ++++++---
 qomemo/CMakeLists.txt | 26 +-------------------------
 qomemo/signal.cpp     |  5 +++++
 qomemo/signal.h       |  2 +-
 4 files changed, 13 insertions(+), 29 deletions(-)
 create mode 100644 qomemo/signal.cpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9b9d151..aafc412 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -43,8 +43,14 @@ else ()
   target_link_libraries(squawk PRIVATE QXmpp::QXmpp)
 endif ()
 
+# Signal
 if (NOT SYSTEM_SIGNAL)
   add_subdirectory(external/signal-protocol-c)
+  add_dependencies(squawk signal-protocol-c)
+  target_link_libraries(squawk PRIVATE signal-protocol-c)
+else ()
+  find_package(Signal REQUIRED)
+  target_link_libraries(squawk PRIVATE Signal::Signal)
 endif ()
 
 ## KIO
@@ -73,9 +79,6 @@ if (WITH_KWALLET)
   endif ()
 endif ()
 
-## Signal (TODO)
-# find_package(Signal REQUIRED)
-
 ## LMDB
 find_package(LMDB REQUIRED)
 
diff --git a/qomemo/CMakeLists.txt b/qomemo/CMakeLists.txt
index 0837eac..dc0e0ea 100644
--- a/qomemo/CMakeLists.txt
+++ b/qomemo/CMakeLists.txt
@@ -1,25 +1 @@
-cmake_minimum_required(VERSION 3.3)
-project(qomemo)
-
-set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
-set(CMAKE_AUTOMOC ON)
-
-find_package(Qt5Core CONFIG REQUIRED)
-find_package(Qt5Xml CONFIG REQUIRED)
-find_package(LMDB REQUIRED)
-
-set(qomemo_SRC signal.h)
-
-add_library(qomemo STATIC ${qomemo_SRC})
-
-if(SYSTEM_QXMPP)
-    get_target_property(QXMPP_INTERFACE_INCLUDE_DIRECTORIES QXmpp::QXmpp INTERFACE_INCLUDE_DIRECTORIES)
-    target_include_directories(qomemo PUBLIC ${QXMPP_INTERFACE_INCLUDE_DIRECTORIES})
-endif()
-
-target_include_directories(qomemo PUBLIC ${CMAKE_SOURCE_DIR}/external/signal-protocol-c/src)
-
-target_link_libraries(qomemo Qt5::Core)
-target_link_libraries(qomemo Qt5::Xml)
-target_link_libraries(qomemo qxmpp)
-target_link_libraries(qomemo lmdb)
+target_sources(squawk PRIVATE signal.h signal.cpp)
\ No newline at end of file
diff --git a/qomemo/signal.cpp b/qomemo/signal.cpp
new file mode 100644
index 0000000..0e5257b
--- /dev/null
+++ b/qomemo/signal.cpp
@@ -0,0 +1,5 @@
+/*
+ * Created by victoria on 2021-05-11.
+ */
+
+#include "signal.h"
diff --git a/qomemo/signal.h b/qomemo/signal.h
index e7e4c8b..4aa5701 100644
--- a/qomemo/signal.h
+++ b/qomemo/signal.h
@@ -4,7 +4,7 @@
 
 #pragma once
 
-#include <signal_protocol.h>
+#include <signal/signal_protocol.h>
 
 namespace Signal
 {

From cb7e2ede75c947a8adeeb58d7a53c6fc2a196de8 Mon Sep 17 00:00:00 2001
From: vae <vae@programming.socks.town>
Date: Wed, 12 May 2021 00:58:39 +0300
Subject: [PATCH 06/20] feat: add OMEMODevices (WIP)

---
 ui/widgets/CMakeLists.txt   |  3 ++
 ui/widgets/omemodevices.cpp |  6 ++++
 ui/widgets/omemodevices.h   | 18 ++++++++++
 ui/widgets/omemodevices.ui  | 72 +++++++++++++++++++++++++++++++++++++
 4 files changed, 99 insertions(+)
 create mode 100644 ui/widgets/omemodevices.cpp
 create mode 100644 ui/widgets/omemodevices.h
 create mode 100644 ui/widgets/omemodevices.ui

diff --git a/ui/widgets/CMakeLists.txt b/ui/widgets/CMakeLists.txt
index 0cacf6f..ac8bc32 100644
--- a/ui/widgets/CMakeLists.txt
+++ b/ui/widgets/CMakeLists.txt
@@ -16,6 +16,9 @@ target_sources(squawk PRIVATE
   newcontact.cpp
   newcontact.h
   newcontact.ui
+  omemodevices.cpp
+  omemodevices.h
+  omemodevices.ui
   room.cpp
   room.h
   )
diff --git a/ui/widgets/omemodevices.cpp b/ui/widgets/omemodevices.cpp
new file mode 100644
index 0000000..630b85a
--- /dev/null
+++ b/ui/widgets/omemodevices.cpp
@@ -0,0 +1,6 @@
+/*
+ * Created by victoria on 2021-05-12.
+ */
+#include "omemodevices.h"
+
+OMEMODevices::OMEMODevices(QWidget *parent) {}
diff --git a/ui/widgets/omemodevices.h b/ui/widgets/omemodevices.h
new file mode 100644
index 0000000..6dd34eb
--- /dev/null
+++ b/ui/widgets/omemodevices.h
@@ -0,0 +1,18 @@
+/*
+ * Created by victoria on 2021-05-12.
+ */
+
+#pragma once
+
+#include <QDialog>
+
+namespace Ui {
+class OMEMODevices;
+}
+
+class OMEMODevices : public QDialog {
+  Q_OBJECT
+public:
+  explicit OMEMODevices(QWidget *parent = nullptr);
+  ~OMEMODevices() override = default;
+};
diff --git a/ui/widgets/omemodevices.ui b/ui/widgets/omemodevices.ui
new file mode 100644
index 0000000..fb923db
--- /dev/null
+++ b/ui/widgets/omemodevices.ui
@@ -0,0 +1,72 @@
+<ui version="4.0" >
+ <author></author>
+ <comment></comment>
+ <exportmacro></exportmacro>
+ <class>Dialog</class>
+ <widget class="QDialog" name="Dialog" >
+  <property name="geometry" >
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>400</width>
+    <height>300</height>
+   </rect>
+  </property>
+  <property name="windowTitle" >
+   <string>Dialog</string>
+  </property>
+  <widget class="QDialogButtonBox" name="buttonBox" >
+   <property name="geometry" >
+    <rect>
+     <x>30</x>
+     <y>240</y>
+     <width>341</width>
+     <height>32</height>
+    </rect>
+   </property>
+   <property name="orientation" >
+    <enum>Qt::Horizontal</enum>
+   </property>
+   <property name="standardButtons" >
+    <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+   </property>
+  </widget>
+ </widget>
+ <pixmapfunction></pixmapfunction>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>Dialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel" >
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel" >
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>Dialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel" >
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel" >
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
+

From 140b0fa6b42aad8287e391058542aa103b3ac674 Mon Sep 17 00:00:00 2001
From: vae <vae@programming.socks.town>
Date: Wed, 12 May 2021 01:44:47 +0300
Subject: [PATCH 07/20] feat: add omemo key list entry WIP

---
 ui/widgets/omemodevices.ui | 133 ++++++++++++++++++++++++++++---------
 1 file changed, 102 insertions(+), 31 deletions(-)

diff --git a/ui/widgets/omemodevices.ui b/ui/widgets/omemodevices.ui
index fb923db..e407655 100644
--- a/ui/widgets/omemodevices.ui
+++ b/ui/widgets/omemodevices.ui
@@ -1,38 +1,110 @@
-<ui version="4.0" >
- <author></author>
- <comment></comment>
- <exportmacro></exportmacro>
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
  <class>Dialog</class>
- <widget class="QDialog" name="Dialog" >
-  <property name="geometry" >
+ <widget class="QDialog" name="Dialog">
+  <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>400</width>
-    <height>300</height>
+    <width>617</width>
+    <height>281</height>
    </rect>
   </property>
-  <property name="windowTitle" >
+  <property name="windowTitle">
    <string>Dialog</string>
   </property>
-  <widget class="QDialogButtonBox" name="buttonBox" >
-   <property name="geometry" >
-    <rect>
-     <x>30</x>
-     <y>240</y>
-     <width>341</width>
-     <height>32</height>
-    </rect>
-   </property>
-   <property name="orientation" >
-    <enum>Qt::Horizontal</enum>
-   </property>
-   <property name="standardButtons" >
-    <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
-   </property>
-  </widget>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QWidget" name="widget" native="true">
+     <layout class="QHBoxLayout" name="horizontalLayout">
+      <item>
+       <widget class="QLabel" name="label_2">
+        <property name="font">
+         <font>
+          <family>Monospace</family>
+          <pointsize>8</pointsize>
+         </font>
+        </property>
+        <property name="text">
+         <string>1234abcd 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd</string>
+        </property>
+        <property name="textFormat">
+         <enum>Qt::PlainText</enum>
+        </property>
+        <property name="wordWrap">
+         <bool>true</bool>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLabel" name="label">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
+        <property name="text">
+         <string>Phone</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QPushButton" name="pushButton_2">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
+        <property name="text">
+         <string/>
+        </property>
+        <property name="icon">
+         <iconset theme="approved"/>
+        </property>
+        <property name="flat">
+         <bool>true</bool>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QPushButton" name="pushButton">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
+        <property name="text">
+         <string/>
+        </property>
+        <property name="icon">
+         <iconset theme="delete"/>
+        </property>
+        <property name="flat">
+         <bool>true</bool>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QListView" name="listView"/>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
  </widget>
- <pixmapfunction></pixmapfunction>
  <resources/>
  <connections>
   <connection>
@@ -41,11 +113,11 @@
    <receiver>Dialog</receiver>
    <slot>accept()</slot>
    <hints>
-    <hint type="sourcelabel" >
+    <hint type="sourcelabel">
      <x>248</x>
      <y>254</y>
     </hint>
-    <hint type="destinationlabel" >
+    <hint type="destinationlabel">
      <x>157</x>
      <y>274</y>
     </hint>
@@ -57,11 +129,11 @@
    <receiver>Dialog</receiver>
    <slot>reject()</slot>
    <hints>
-    <hint type="sourcelabel" >
+    <hint type="sourcelabel">
      <x>316</x>
      <y>260</y>
     </hint>
-    <hint type="destinationlabel" >
+    <hint type="destinationlabel">
      <x>286</x>
      <y>274</y>
     </hint>
@@ -69,4 +141,3 @@
   </connection>
  </connections>
 </ui>
-

From a6254d88b34ad943e822ad00093cdf601196def1 Mon Sep 17 00:00:00 2001
From: vae <vae@programming.socks.town>
Date: Wed, 12 May 2021 10:05:17 +0300
Subject: [PATCH 08/20] feat: OMEMO devices ui mock

---
 ui/CMakeLists.txt                    |   1 +
 ui/omemo/CMakeLists.txt              |   5 +
 ui/omemo/omemodevices.cpp            |  13 +
 ui/{widgets => omemo}/omemodevices.h |   5 +-
 ui/omemo/omemodevices.ui             | 508 +++++++++++++++++++++++++++
 ui/squawk.cpp                        |  11 +-
 ui/squawk.h                          |   1 +
 ui/widgets/CMakeLists.txt            |   3 -
 ui/widgets/omemodevices.cpp          |   6 -
 ui/widgets/omemodevices.ui           | 143 --------
 10 files changed, 542 insertions(+), 154 deletions(-)
 create mode 100644 ui/omemo/CMakeLists.txt
 create mode 100644 ui/omemo/omemodevices.cpp
 rename ui/{widgets => omemo}/omemodevices.h (74%)
 create mode 100644 ui/omemo/omemodevices.ui
 delete mode 100644 ui/widgets/omemodevices.cpp
 delete mode 100644 ui/widgets/omemodevices.ui

diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt
index 36207b6..222f8d7 100644
--- a/ui/CMakeLists.txt
+++ b/ui/CMakeLists.txt
@@ -3,3 +3,4 @@ target_sources(squawk PRIVATE squawk.cpp squawk.h squawk.ui)
 add_subdirectory(models)
 add_subdirectory(utils)
 add_subdirectory(widgets)
+add_subdirectory(omemo)
\ No newline at end of file
diff --git a/ui/omemo/CMakeLists.txt b/ui/omemo/CMakeLists.txt
new file mode 100644
index 0000000..41afa04
--- /dev/null
+++ b/ui/omemo/CMakeLists.txt
@@ -0,0 +1,5 @@
+target_sources(squawk PRIVATE
+  omemodevices.cpp
+  omemodevices.h
+  omemodevices.ui
+  )
\ No newline at end of file
diff --git a/ui/omemo/omemodevices.cpp b/ui/omemo/omemodevices.cpp
new file mode 100644
index 0000000..e746f5b
--- /dev/null
+++ b/ui/omemo/omemodevices.cpp
@@ -0,0 +1,13 @@
+/*
+ * Created by victoria on 2021-05-12.
+ */
+
+#include "omemodevices.h"
+#include "ui_omemodevices.h"
+
+OMEMODevices::OMEMODevices(QWidget *parent)
+    : QDialog(parent), m_ui(new Ui::OMEMODevices()) {
+  m_ui->setupUi(this);
+}
+
+OMEMODevices::~OMEMODevices() {}
diff --git a/ui/widgets/omemodevices.h b/ui/omemo/omemodevices.h
similarity index 74%
rename from ui/widgets/omemodevices.h
rename to ui/omemo/omemodevices.h
index 6dd34eb..b107baa 100644
--- a/ui/widgets/omemodevices.h
+++ b/ui/omemo/omemodevices.h
@@ -14,5 +14,8 @@ class OMEMODevices : public QDialog {
   Q_OBJECT
 public:
   explicit OMEMODevices(QWidget *parent = nullptr);
-  ~OMEMODevices() override = default;
+  ~OMEMODevices() override;
+
+private:
+  QScopedPointer<Ui::OMEMODevices> m_ui;
 };
diff --git a/ui/omemo/omemodevices.ui b/ui/omemo/omemodevices.ui
new file mode 100644
index 0000000..c15dc9d
--- /dev/null
+++ b/ui/omemo/omemodevices.ui
@@ -0,0 +1,508 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>OMEMODevices</class>
+ <widget class="QDialog" name="OMEMODevices">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>560</width>
+    <height>357</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>OMEMO Devices</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QLabel" name="label_5">
+     <property name="text">
+      <string>List of OMEMO fingerprints for fooooo@bar.com</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QScrollArea" name="scrollArea">
+     <property name="widgetResizable">
+      <bool>true</bool>
+     </property>
+     <widget class="QWidget" name="scrollAreaWidgetContents">
+      <property name="geometry">
+       <rect>
+        <x>0</x>
+        <y>0</y>
+        <width>540</width>
+        <height>281</height>
+       </rect>
+      </property>
+      <property name="sizePolicy">
+       <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+        <horstretch>0</horstretch>
+        <verstretch>0</verstretch>
+       </sizepolicy>
+      </property>
+      <layout class="QVBoxLayout" name="verticalLayout_2">
+       <item>
+        <widget class="QFrame" name="frame_3">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="styleSheet">
+          <string notr="true">background-color: rgb(6, 88, 6)</string>
+         </property>
+         <property name="frameShape">
+          <enum>QFrame::Panel</enum>
+         </property>
+         <property name="frameShadow">
+          <enum>QFrame::Raised</enum>
+         </property>
+         <layout class="QHBoxLayout" name="horizontalLayout_3">
+          <item>
+           <widget class="QLabel" name="label_6">
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <property name="minimumSize">
+             <size>
+              <width>240</width>
+              <height>0</height>
+             </size>
+            </property>
+            <property name="font">
+             <font>
+              <family>Monospace</family>
+              <pointsize>8</pointsize>
+             </font>
+            </property>
+            <property name="text">
+             <string>1234abcd 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd</string>
+            </property>
+            <property name="textFormat">
+             <enum>Qt::PlainText</enum>
+            </property>
+            <property name="wordWrap">
+             <bool>true</bool>
+            </property>
+            <property name="textInteractionFlags">
+             <set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QLabel" name="label_7">
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <property name="text">
+             <string>Squawk</string>
+            </property>
+            <property name="textInteractionFlags">
+             <set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QPushButton" name="pushButton_6">
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <property name="text">
+             <string/>
+            </property>
+            <property name="icon">
+             <iconset theme="view-barcode-qr">
+              <normaloff>.</normaloff>.</iconset>
+            </property>
+            <property name="flat">
+             <bool>true</bool>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QPushButton" name="pushButton_9">
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <property name="text">
+             <string/>
+            </property>
+            <property name="icon">
+             <iconset theme="approved">
+              <normaloff>.</normaloff>.</iconset>
+            </property>
+            <property name="flat">
+             <bool>true</bool>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QPushButton" name="pushButton_10">
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <property name="text">
+             <string/>
+            </property>
+            <property name="icon">
+             <iconset theme="edit-delete-remove">
+              <normaloff>.</normaloff>.</iconset>
+            </property>
+            <property name="flat">
+             <bool>true</bool>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </widget>
+       </item>
+       <item>
+        <widget class="QFrame" name="frame_2">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="frameShape">
+          <enum>QFrame::Panel</enum>
+         </property>
+         <property name="frameShadow">
+          <enum>QFrame::Raised</enum>
+         </property>
+         <layout class="QHBoxLayout" name="horizontalLayout_2">
+          <item>
+           <widget class="QLabel" name="label_3">
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <property name="minimumSize">
+             <size>
+              <width>240</width>
+              <height>0</height>
+             </size>
+            </property>
+            <property name="font">
+             <font>
+              <family>Monospace</family>
+              <pointsize>8</pointsize>
+             </font>
+            </property>
+            <property name="text">
+             <string>1234abcd 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd</string>
+            </property>
+            <property name="textFormat">
+             <enum>Qt::PlainText</enum>
+            </property>
+            <property name="wordWrap">
+             <bool>true</bool>
+            </property>
+            <property name="textInteractionFlags">
+             <set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QLabel" name="label_4">
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <property name="text">
+             <string>Dino</string>
+            </property>
+            <property name="textInteractionFlags">
+             <set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QPushButton" name="pushButton_5">
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <property name="text">
+             <string/>
+            </property>
+            <property name="icon">
+             <iconset theme="view-barcode-qr">
+              <normaloff>.</normaloff>.</iconset>
+            </property>
+            <property name="flat">
+             <bool>true</bool>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QPushButton" name="pushButton_7">
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <property name="text">
+             <string/>
+            </property>
+            <property name="icon">
+             <iconset theme="approved">
+              <normaloff>.</normaloff>.</iconset>
+            </property>
+            <property name="flat">
+             <bool>true</bool>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QPushButton" name="pushButton_8">
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <property name="text">
+             <string/>
+            </property>
+            <property name="icon">
+             <iconset theme="edit-delete-remove">
+              <normaloff>.</normaloff>.</iconset>
+            </property>
+            <property name="flat">
+             <bool>true</bool>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </widget>
+       </item>
+       <item>
+        <widget class="QFrame" name="frame">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="autoFillBackground">
+          <bool>false</bool>
+         </property>
+         <property name="styleSheet">
+          <string notr="true">background: rgb(111, 47, 47)</string>
+         </property>
+         <property name="frameShape">
+          <enum>QFrame::Panel</enum>
+         </property>
+         <property name="frameShadow">
+          <enum>QFrame::Raised</enum>
+         </property>
+         <property name="lineWidth">
+          <number>1</number>
+         </property>
+         <layout class="QHBoxLayout" name="horizontalLayout">
+          <item>
+           <widget class="QLabel" name="label_2">
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Fixed" vsizetype="Minimum">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <property name="minimumSize">
+             <size>
+              <width>240</width>
+              <height>0</height>
+             </size>
+            </property>
+            <property name="font">
+             <font>
+              <family>Monospace</family>
+              <pointsize>8</pointsize>
+             </font>
+            </property>
+            <property name="text">
+             <string>1234abcd 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd</string>
+            </property>
+            <property name="textFormat">
+             <enum>Qt::PlainText</enum>
+            </property>
+            <property name="wordWrap">
+             <bool>true</bool>
+            </property>
+            <property name="textInteractionFlags">
+             <set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QLabel" name="label">
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <property name="text">
+             <string>Conversations</string>
+            </property>
+            <property name="textInteractionFlags">
+             <set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QPushButton" name="pushButton_4">
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <property name="text">
+             <string/>
+            </property>
+            <property name="icon">
+             <iconset theme="view-barcode-qr">
+              <normaloff>.</normaloff>.</iconset>
+            </property>
+            <property name="flat">
+             <bool>true</bool>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QPushButton" name="pushButton_2">
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <property name="text">
+             <string/>
+            </property>
+            <property name="icon">
+             <iconset theme="approved">
+              <normaloff>.</normaloff>.</iconset>
+            </property>
+            <property name="flat">
+             <bool>true</bool>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QPushButton" name="pushButton">
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <property name="text">
+             <string/>
+            </property>
+            <property name="icon">
+             <iconset theme="edit-delete-remove">
+              <normaloff>.</normaloff>.</iconset>
+            </property>
+            <property name="flat">
+             <bool>true</bool>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </widget>
+       </item>
+       <item>
+        <spacer name="verticalSpacer">
+         <property name="orientation">
+          <enum>Qt::Vertical</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>20</width>
+           <height>40</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+      </layout>
+     </widget>
+    </widget>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Close</set>
+     </property>
+     <property name="centerButtons">
+      <bool>false</bool>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>OMEMODevices</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>OMEMODevices</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index fb79592..13c452a 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -20,6 +20,7 @@
 #include "ui_squawk.h"
 #include <QDebug>
 #include <QIcon>
+#include <ui/omemo/omemodevices.h>
 
 Squawk::Squawk(QWidget *parent) :
     QMainWindow(parent),
@@ -60,7 +61,8 @@ Squawk::Squawk(QWidget *parent) :
     connect(m_ui->roster, &QTreeView::collapsed, this, &Squawk::onItemCollepsed);
     connect(m_ui->roster->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &Squawk::onRosterSelectionChanged);
     connect(&rosterModel, &Models::Roster::unnoticedMessage, this, &Squawk::onUnnoticedMessage);
-    
+    connect(m_ui->actionDeviceList, &QAction::triggered, this, &Squawk::onOMEMODevices);
+
     connect(rosterModel.accountsModel, &Models::Accounts::sizeChanged, this, &Squawk::onAccountsSizeChanged);
     connect(&rosterModel, &Models::Roster::requestArchive, this, &Squawk::onRequestArchive);
     connect(&rosterModel, &Models::Roster::fileDownloadRequest, this, &Squawk::fileDownloadRequest);
@@ -134,6 +136,13 @@ void Squawk::onNewContact()
     nc->exec();
 }
 
+void Squawk::onOMEMODevices() {
+  auto od = new OMEMODevices(this);
+
+  od->setAttribute(Qt::WA_DeleteOnClose);
+  od->show();
+}
+
 void Squawk::onNewConference()
 {
     JoinConference* jc = new JoinConference(rosterModel.accountsModel, this);
diff --git a/ui/squawk.h b/ui/squawk.h
index 15d3f82..a4c130e 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -155,6 +155,7 @@ private slots:
     void onPasswordPromptRejected();
     void onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous);
     void onContextAboutToHide();
+    void onOMEMODevices();
     
     void onUnnoticedMessage(const QString& account, const Shared::Message& msg);
     
diff --git a/ui/widgets/CMakeLists.txt b/ui/widgets/CMakeLists.txt
index ac8bc32..0cacf6f 100644
--- a/ui/widgets/CMakeLists.txt
+++ b/ui/widgets/CMakeLists.txt
@@ -16,9 +16,6 @@ target_sources(squawk PRIVATE
   newcontact.cpp
   newcontact.h
   newcontact.ui
-  omemodevices.cpp
-  omemodevices.h
-  omemodevices.ui
   room.cpp
   room.h
   )
diff --git a/ui/widgets/omemodevices.cpp b/ui/widgets/omemodevices.cpp
deleted file mode 100644
index 630b85a..0000000
--- a/ui/widgets/omemodevices.cpp
+++ /dev/null
@@ -1,6 +0,0 @@
-/*
- * Created by victoria on 2021-05-12.
- */
-#include "omemodevices.h"
-
-OMEMODevices::OMEMODevices(QWidget *parent) {}
diff --git a/ui/widgets/omemodevices.ui b/ui/widgets/omemodevices.ui
deleted file mode 100644
index e407655..0000000
--- a/ui/widgets/omemodevices.ui
+++ /dev/null
@@ -1,143 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>Dialog</class>
- <widget class="QDialog" name="Dialog">
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>617</width>
-    <height>281</height>
-   </rect>
-  </property>
-  <property name="windowTitle">
-   <string>Dialog</string>
-  </property>
-  <layout class="QVBoxLayout" name="verticalLayout">
-   <item>
-    <widget class="QWidget" name="widget" native="true">
-     <layout class="QHBoxLayout" name="horizontalLayout">
-      <item>
-       <widget class="QLabel" name="label_2">
-        <property name="font">
-         <font>
-          <family>Monospace</family>
-          <pointsize>8</pointsize>
-         </font>
-        </property>
-        <property name="text">
-         <string>1234abcd 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd</string>
-        </property>
-        <property name="textFormat">
-         <enum>Qt::PlainText</enum>
-        </property>
-        <property name="wordWrap">
-         <bool>true</bool>
-        </property>
-       </widget>
-      </item>
-      <item>
-       <widget class="QLabel" name="label">
-        <property name="sizePolicy">
-         <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
-          <horstretch>0</horstretch>
-          <verstretch>0</verstretch>
-         </sizepolicy>
-        </property>
-        <property name="text">
-         <string>Phone</string>
-        </property>
-       </widget>
-      </item>
-      <item>
-       <widget class="QPushButton" name="pushButton_2">
-        <property name="sizePolicy">
-         <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
-          <horstretch>0</horstretch>
-          <verstretch>0</verstretch>
-         </sizepolicy>
-        </property>
-        <property name="text">
-         <string/>
-        </property>
-        <property name="icon">
-         <iconset theme="approved"/>
-        </property>
-        <property name="flat">
-         <bool>true</bool>
-        </property>
-       </widget>
-      </item>
-      <item>
-       <widget class="QPushButton" name="pushButton">
-        <property name="sizePolicy">
-         <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
-          <horstretch>0</horstretch>
-          <verstretch>0</verstretch>
-         </sizepolicy>
-        </property>
-        <property name="text">
-         <string/>
-        </property>
-        <property name="icon">
-         <iconset theme="delete"/>
-        </property>
-        <property name="flat">
-         <bool>true</bool>
-        </property>
-       </widget>
-      </item>
-     </layout>
-    </widget>
-   </item>
-   <item>
-    <widget class="QListView" name="listView"/>
-   </item>
-   <item>
-    <widget class="QDialogButtonBox" name="buttonBox">
-     <property name="orientation">
-      <enum>Qt::Horizontal</enum>
-     </property>
-     <property name="standardButtons">
-      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
-     </property>
-    </widget>
-   </item>
-  </layout>
- </widget>
- <resources/>
- <connections>
-  <connection>
-   <sender>buttonBox</sender>
-   <signal>accepted()</signal>
-   <receiver>Dialog</receiver>
-   <slot>accept()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>248</x>
-     <y>254</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>157</x>
-     <y>274</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>buttonBox</sender>
-   <signal>rejected()</signal>
-   <receiver>Dialog</receiver>
-   <slot>reject()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>316</x>
-     <y>260</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>286</x>
-     <y>274</y>
-    </hint>
-   </hints>
-  </connection>
- </connections>
-</ui>

From b22a4c8ca3dc1870d0bc88fb94cc381eacbfe45d Mon Sep 17 00:00:00 2001
From: vae <vae@programming.socks.town>
Date: Wed, 12 May 2021 12:33:35 +0300
Subject: [PATCH 09/20] feat(OMEMO): add Device, DeviceList, PreKey, Bundle +
 XML

---
 qomemo/CMakeLists.txt |   7 +-
 qomemo/qomemo.cpp     | 243 ++++++++++++++++++++++++++++++++++++++++++
 qomemo/qomemo.h       |  56 ++++++++++
 3 files changed, 305 insertions(+), 1 deletion(-)
 create mode 100644 qomemo/qomemo.cpp
 create mode 100644 qomemo/qomemo.h

diff --git a/qomemo/CMakeLists.txt b/qomemo/CMakeLists.txt
index dc0e0ea..f17475c 100644
--- a/qomemo/CMakeLists.txt
+++ b/qomemo/CMakeLists.txt
@@ -1 +1,6 @@
-target_sources(squawk PRIVATE signal.h signal.cpp)
\ No newline at end of file
+target_sources(squawk PRIVATE
+  signal.h
+  signal.cpp
+  qomemo.cpp
+  qomemo.h
+  )
\ No newline at end of file
diff --git a/qomemo/qomemo.cpp b/qomemo/qomemo.cpp
new file mode 100644
index 0000000..1695c04
--- /dev/null
+++ b/qomemo/qomemo.cpp
@@ -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);
+  }
+}
diff --git a/qomemo/qomemo.h b/qomemo/qomemo.h
new file mode 100644
index 0000000..8955d1e
--- /dev/null
+++ b/qomemo/qomemo.h
@@ -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

From 6721b62629ab036df7230fc98ac70486e3d1825c Mon Sep 17 00:00:00 2001
From: vae <vae@programming.socks.town>
Date: Wed, 12 May 2021 15:00:41 +0300
Subject: [PATCH 10/20] feat(OMEMO): qxmppfactories, refactoring

---
 qomemo/CMakeLists.txt     |   2 +
 qomemo/qomemo.cpp         | 189 +++++++++++++++++++++++---------------
 qomemo/qomemo.h           |  40 +++++++-
 qomemo/sce.cpp            |  24 +++++
 qomemo/sce.h              |  14 +++
 shared/CMakeLists.txt     |   2 +
 shared/qxmppfactories.cpp |  75 +++++++++++++++
 shared/qxmppfactories.h   |  25 +++++
 8 files changed, 294 insertions(+), 77 deletions(-)
 create mode 100644 qomemo/sce.cpp
 create mode 100644 qomemo/sce.h
 create mode 100644 shared/qxmppfactories.cpp
 create mode 100644 shared/qxmppfactories.h

diff --git a/qomemo/CMakeLists.txt b/qomemo/CMakeLists.txt
index f17475c..e8e4502 100644
--- a/qomemo/CMakeLists.txt
+++ b/qomemo/CMakeLists.txt
@@ -3,4 +3,6 @@ target_sources(squawk PRIVATE
   signal.cpp
   qomemo.cpp
   qomemo.h
+  sce.cpp
+  sce.h
   )
\ No newline at end of file
diff --git a/qomemo/qomemo.cpp b/qomemo/qomemo.cpp
index 1695c04..9d4266a 100644
--- a/qomemo/qomemo.cpp
+++ b/qomemo/qomemo.cpp
@@ -3,77 +3,16 @@
  */
 
 #include "qomemo.h"
+#include "sce.h"
+#include <shared/qxmppfactories.h>
 
+#include <QBuffer>
 #include <QDebug>
+#include <QDomDocument>
 
-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;
-  }
+using namespace QXmpp::Factories;
 
-  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 {
+QXmppElement QXmpp::Omemo::DeviceList::toXml() const {
   auto element = createElement("devices", "urn:xmpp:omemo:1");
 
   for (const auto &d : devices) {
@@ -83,7 +22,7 @@ QXmppElement QOmemo::DeviceList::toXml() const {
   return element;
 }
 
-QXmppIq QOmemo::DeviceList::toIq() const {
+QXmppIq QXmpp::Omemo::DeviceList::toIq() const {
   QXmppIq iq{};
 
   iq.setType(QXmppIq::Set);
@@ -105,7 +44,7 @@ QXmppIq QOmemo::DeviceList::toIq() const {
   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"))
     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");
   result.setAttribute("id", QString::number(id));
 
@@ -132,7 +71,7 @@ QXmppElement QOmemo::Device::toXml() const {
   return result;
 }
 
-void QOmemo::Device::fromXml(const QXmppElement &element) {
+void QXmpp::Omemo::Device::fromXml(const QXmppElement &element) {
   if (!elementMatches(element, "device"))
     return;
 
@@ -140,7 +79,7 @@ void QOmemo::Device::fromXml(const QXmppElement &element) {
   label = element.attribute("label");
 }
 
-QXmppElement QOmemo::PreKey::toXml() const {
+QXmppElement QXmpp::Omemo::PreKey::toXml() const {
   auto pk = createElement("pk");
   pk.setAttribute("id", QString::number(id));
   // TODO: Base64
@@ -149,7 +88,7 @@ QXmppElement QOmemo::PreKey::toXml() const {
   return pk;
 }
 
-void QOmemo::PreKey::fromXml(const QXmppElement &element) {
+void QXmpp::Omemo::PreKey::fromXml(const QXmppElement &element) {
   if (!elementMatches(element, "pk"))
     return;
 
@@ -158,7 +97,7 @@ void QOmemo::PreKey::fromXml(const QXmppElement &element) {
   data = element.value();
 }
 
-QXmppElement QOmemo::Bundle::toXml() const {
+QXmppElement QXmpp::Omemo::Bundle::toXml() const {
   auto spkNode = createElement("spk");
   spkNode.setAttribute("id", QString::number(spkId));
   spkNode.setValue(spk);
@@ -183,7 +122,7 @@ QXmppElement QOmemo::Bundle::toXml() const {
   return result;
 }
 
-QXmppIq QOmemo::Bundle::toIq() const {
+QXmppIq QXmpp::Omemo::Bundle::toIq() const {
   QXmppIq iq{};
 
   iq.setType(QXmppIq::Set);
@@ -205,7 +144,7 @@ QXmppIq QOmemo::Bundle::toIq() const {
   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"))
     return;
 
@@ -241,3 +180,101 @@ void QOmemo::Bundle::fromXml(const QXmppElement &element) {
     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;
+}
diff --git a/qomemo/qomemo.h b/qomemo/qomemo.h
index 8955d1e..906d0ba 100644
--- a/qomemo/qomemo.h
+++ b/qomemo/qomemo.h
@@ -6,7 +6,10 @@
 
 #include <QXmppPubSubIq.h>
 
-namespace QOmemo {
+#include <QDateTime>
+#include <QXmppMessage.h>
+
+namespace QXmpp::Omemo {
 
 class Device {
 public:
@@ -40,6 +43,8 @@ public:
 
 class Bundle {
 public:
+  [[nodiscard]] static QXmppPubSubIq fetchDeviceBundleIq(int deviceId);
+
   [[nodiscard]] QXmppElement toXml() const;
   [[nodiscard]] QXmppIq toIq() const;
   void fromXml(const QXmppElement &element);
@@ -53,4 +58,37 @@ public:
   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
diff --git a/qomemo/sce.cpp b/qomemo/sce.cpp
new file mode 100644
index 0000000..decb8b3
--- /dev/null
+++ b/qomemo/sce.cpp
@@ -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;
+}
diff --git a/qomemo/sce.h b/qomemo/sce.h
new file mode 100644
index 0000000..2cea049
--- /dev/null
+++ b/qomemo/sce.h
@@ -0,0 +1,14 @@
+/*
+ * Created by victoria on 2021-05-12.
+ */
+
+#pragma once
+
+#include <QXmppElement.h>
+#include <QDateTime>
+
+namespace QXmpp::Sce {
+
+QString generatePadding();
+
+}
diff --git a/shared/CMakeLists.txt b/shared/CMakeLists.txt
index a36b516..97ed619 100644
--- a/shared/CMakeLists.txt
+++ b/shared/CMakeLists.txt
@@ -16,4 +16,6 @@ target_sources(squawk PRIVATE
   utils.h
   vcard.cpp
   vcard.h
+  qxmppfactories.cpp
+  qxmppfactories.h
   )
diff --git a/shared/qxmppfactories.cpp b/shared/qxmppfactories.cpp
new file mode 100644
index 0000000..dc53388
--- /dev/null
+++ b/shared/qxmppfactories.cpp
@@ -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;
+}
\ No newline at end of file
diff --git a/shared/qxmppfactories.h b/shared/qxmppfactories.h
new file mode 100644
index 0000000..d7d78e6
--- /dev/null
+++ b/shared/qxmppfactories.h
@@ -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

From b1a8f162ceee0978c14362d065572a3054b4864e Mon Sep 17 00:00:00 2001
From: vae <vae@programming.socks.town>
Date: Wed, 12 May 2021 15:50:52 +0300
Subject: [PATCH 11/20] feat(OMEMO): DeviceKeyStorage, DeviceService,
 UserDeviceList stubs

---
 qomemo/CMakeLists.txt         |  6 ++++++
 qomemo/device_key_storage.cpp | 15 +++++++++++++++
 qomemo/device_key_storage.h   | 18 ++++++++++++++++++
 qomemo/device_service.cpp     | 30 ++++++++++++++++++++++++++++++
 qomemo/device_service.h       | 31 +++++++++++++++++++++++++++++++
 qomemo/qomemo.h               |  2 +-
 qomemo/user_device_list.cpp   | 10 ++++++++++
 qomemo/user_device_list.h     | 17 +++++++++++++++++
 8 files changed, 128 insertions(+), 1 deletion(-)
 create mode 100644 qomemo/device_key_storage.cpp
 create mode 100644 qomemo/device_key_storage.h
 create mode 100644 qomemo/device_service.cpp
 create mode 100644 qomemo/device_service.h
 create mode 100644 qomemo/user_device_list.cpp
 create mode 100644 qomemo/user_device_list.h

diff --git a/qomemo/CMakeLists.txt b/qomemo/CMakeLists.txt
index e8e4502..40abc25 100644
--- a/qomemo/CMakeLists.txt
+++ b/qomemo/CMakeLists.txt
@@ -1,8 +1,14 @@
 target_sources(squawk PRIVATE
+  device_key_storage.cpp
+  device_key_storage.h
+  device_service.cpp
+  device_service.h
   signal.h
   signal.cpp
   qomemo.cpp
   qomemo.h
   sce.cpp
   sce.h
+  user_device_list.cpp
+  user_device_list.h
   )
\ No newline at end of file
diff --git a/qomemo/device_key_storage.cpp b/qomemo/device_key_storage.cpp
new file mode 100644
index 0000000..1ba017e
--- /dev/null
+++ b/qomemo/device_key_storage.cpp
@@ -0,0 +1,15 @@
+/*
+ * Created by victoria on 2021-05-12.
+ */
+
+#include "device_key_storage.h"
+#include <QRandomGenerator>
+
+int QXmpp::Omemo::DeviceKeyStorage::generateDeviceId() {
+  QRandomGenerator random{};
+
+  return 1 + random.bounded(INT32_MAX - 1);
+}
+
+QXmpp::Omemo::DeviceKeyStorage::DeviceKeyStorage(int deviceId)
+    : deviceId(deviceId) {}
diff --git a/qomemo/device_key_storage.h b/qomemo/device_key_storage.h
new file mode 100644
index 0000000..a0d8ce2
--- /dev/null
+++ b/qomemo/device_key_storage.h
@@ -0,0 +1,18 @@
+/*
+ * Created by victoria on 2021-05-12.
+ */
+
+#pragma once
+
+namespace QXmpp::Omemo {
+
+class DeviceKeyStorage {
+public:
+  static int generateDeviceId();
+
+  explicit DeviceKeyStorage(int deviceId);
+
+  const int deviceId;
+};
+
+} // namespace QXmpp::Omemo
diff --git a/qomemo/device_service.cpp b/qomemo/device_service.cpp
new file mode 100644
index 0000000..4333d7f
--- /dev/null
+++ b/qomemo/device_service.cpp
@@ -0,0 +1,30 @@
+/*
+ * Created by victoria on 2021-05-12.
+ */
+
+#include "device_service.h"
+
+#include <QXmppClient.h>
+#include <QXmppPubSubIq.h>
+
+#include <QDebug>
+
+QXmpp::Omemo::DeviceService::DeviceService(QXmppClient &client, QObject *parent)
+    : QObject(parent), client(client) {
+  connect(&client, &QXmppClient::iqReceived, this,
+          &DeviceService::onIqReceived);
+}
+
+void QXmpp::Omemo::DeviceService::onIqReceived(const QXmppIq &iq) {
+  // Update OMEMO device list
+}
+
+void QXmpp::Omemo::DeviceService::fetch() {
+  QXmppPubSubIq fetchOwnDevices{};
+  fetchOwnDevices.setFrom(client.configuration().jid());
+  fetchOwnDevices.setTo(client.configuration().jidBare());
+  fetchOwnDevices.setType(QXmppIq::Get);
+  fetchOwnDevices.setQueryNode("urn:xmpp:omemo:1:devices");
+
+  client.sendPacket(fetchOwnDevices);
+}
diff --git a/qomemo/device_service.h b/qomemo/device_service.h
new file mode 100644
index 0000000..fc2e4d5
--- /dev/null
+++ b/qomemo/device_service.h
@@ -0,0 +1,31 @@
+/*
+ * Created by victoria on 2021-05-12.
+ */
+
+#pragma once
+
+#include "user_device_list.h"
+
+#include <QXmppClient.h>
+
+namespace QXmpp::Omemo {
+
+class DeviceService : public QObject {
+  Q_OBJECT
+
+public:
+  DeviceService(QXmppClient& client, QObject *parent);
+
+  void fetch();
+
+public slots:
+  void onIqReceived(const QXmppIq& iq);
+
+private:
+  void announce();
+
+  QXmppClient& client;
+  QMap<QString, UserDeviceList> device_lists{};
+};
+
+} // namespace QXmpp::Omemo
diff --git a/qomemo/qomemo.h b/qomemo/qomemo.h
index 906d0ba..e815d11 100644
--- a/qomemo/qomemo.h
+++ b/qomemo/qomemo.h
@@ -91,4 +91,4 @@ public:
   QXmppMessage message{};
 };
 
-} // namespace QOmemo
+} // namespace QXmpp::Omemo
diff --git a/qomemo/user_device_list.cpp b/qomemo/user_device_list.cpp
new file mode 100644
index 0000000..9a3f18a
--- /dev/null
+++ b/qomemo/user_device_list.cpp
@@ -0,0 +1,10 @@
+/*
+ * Created by victoria on 2021-05-12.
+ */
+
+#include "user_device_list.h"
+
+#include <utility>
+
+QXmpp::Omemo::UserDeviceList::UserDeviceList(QString jid)
+    : jid(std::move(jid)) {}
diff --git a/qomemo/user_device_list.h b/qomemo/user_device_list.h
new file mode 100644
index 0000000..26a88ca
--- /dev/null
+++ b/qomemo/user_device_list.h
@@ -0,0 +1,17 @@
+/*
+ * Created by victoria on 2021-05-12.
+ */
+
+#pragma once
+
+#include <QString>
+namespace QXmpp::Omemo {
+
+class UserDeviceList {
+public:
+  explicit UserDeviceList(QString jid);
+
+  const QString jid;
+};
+
+} // namespace QXmpp::Omemo

From 006752b31ca21cd2b911911d002a09d6c4d3b322 Mon Sep 17 00:00:00 2001
From: vae <vae@programming.socks.town>
Date: Wed, 12 May 2021 17:33:34 +0300
Subject: [PATCH 12/20] feat(OMEMO): QXmppClientExtension for OMEMO

---
 core/account.cpp               |  6 ++-
 core/account.h                 | 21 ++++++----
 qomemo/CMakeLists.txt          |  2 +
 qomemo/device_service.cpp      | 27 +++---------
 qomemo/device_service.h        | 10 ++---
 qomemo/qomemo.cpp              | 76 ++++++++++++++--------------------
 qomemo/qomemo.h                | 21 +++-------
 qomemo/qxmpp_omemo_manager.cpp | 66 +++++++++++++++++++++++++++++
 qomemo/qxmpp_omemo_manager.h   | 36 ++++++++++++++++
 9 files changed, 167 insertions(+), 98 deletions(-)
 create mode 100644 qomemo/qxmpp_omemo_manager.cpp
 create mode 100644 qomemo/qxmpp_omemo_manager.h

diff --git a/core/account.cpp b/core/account.cpp
index 5ce29ee..d38e889 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -47,7 +47,8 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     network(p_net),
     passwordType(Shared::AccountPassword::plain),
     mh(new MessageHandler(this)),
-    rh(new RosterHandler(this))
+    rh(new RosterHandler(this)),
+    omemo(new QXmpp::Omemo::Manager())
 {
     config.setUser(p_login);
     config.setDomain(p_server);
@@ -90,7 +91,8 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     
     client.addExtension(rcpm);
     QObject::connect(rcpm, &QXmppMessageReceiptManager::messageDelivered, mh, &MessageHandler::onReceiptReceived);
-    
+
+    client.addExtension(omemo.get());
     
     QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
     path += "/" + name;
diff --git a/core/account.h b/core/account.h
index a0db9f9..2a3816e 100644
--- a/core/account.h
+++ b/core/account.h
@@ -30,23 +30,24 @@
 #include <map>
 #include <set>
 
-#include <QXmppRosterManager.h>
-#include <QXmppCarbonManager.h>
-#include <QXmppDiscoveryManager.h>
-#include <QXmppMamManager.h>
-#include <QXmppMucManager.h>
-#include <QXmppClient.h>
 #include <QXmppBookmarkManager.h>
 #include <QXmppBookmarkSet.h>
+#include <QXmppCarbonManager.h>
+#include <QXmppClient.h>
+#include <QXmppDiscoveryManager.h>
+#include <QXmppMamManager.h>
+#include <QXmppMessageReceiptManager.h>
+#include <QXmppMucManager.h>
+#include <QXmppRosterManager.h>
 #include <QXmppUploadRequestManager.h>
 #include <QXmppVCardIq.h>
 #include <QXmppVCardManager.h>
-#include <QXmppMessageReceiptManager.h>
+#include <qomemo/qxmpp_omemo_manager.h>
 
-#include "shared/shared.h"
-#include "contact.h"
 #include "conference.h"
+#include "contact.h"
 #include "networkaccess.h"
+#include "shared/shared.h"
 
 #include "handlers/messagehandler.h"
 #include "handlers/rosterhandler.h"
@@ -165,6 +166,8 @@ private:
     
     MessageHandler* mh;
     RosterHandler* rh;
+
+    QScopedPointer<QXmpp::Omemo::Manager> omemo;
     
 private slots:
     void onClientStateChange(QXmppClient::State state);
diff --git a/qomemo/CMakeLists.txt b/qomemo/CMakeLists.txt
index 40abc25..1512264 100644
--- a/qomemo/CMakeLists.txt
+++ b/qomemo/CMakeLists.txt
@@ -11,4 +11,6 @@ target_sources(squawk PRIVATE
   sce.h
   user_device_list.cpp
   user_device_list.h
+  qxmpp_omemo_manager.cpp
+  qxmpp_omemo_manager.h
   )
\ No newline at end of file
diff --git a/qomemo/device_service.cpp b/qomemo/device_service.cpp
index 4333d7f..5e9a460 100644
--- a/qomemo/device_service.cpp
+++ b/qomemo/device_service.cpp
@@ -4,27 +4,12 @@
 
 #include "device_service.h"
 
-#include <QXmppClient.h>
-#include <QXmppPubSubIq.h>
+QXmpp::Omemo::DeviceService::DeviceService(QObject *parent) : QObject(parent) {}
 
-#include <QDebug>
+void QXmpp::Omemo::DeviceService::onDeviceListReceived(
+    const QString &jid, const QXmpp::Omemo::DeviceList &list) {
 
-QXmpp::Omemo::DeviceService::DeviceService(QXmppClient &client, QObject *parent)
-    : QObject(parent), client(client) {
-  connect(&client, &QXmppClient::iqReceived, this,
-          &DeviceService::onIqReceived);
-}
-
-void QXmpp::Omemo::DeviceService::onIqReceived(const QXmppIq &iq) {
-  // Update OMEMO device list
-}
-
-void QXmpp::Omemo::DeviceService::fetch() {
-  QXmppPubSubIq fetchOwnDevices{};
-  fetchOwnDevices.setFrom(client.configuration().jid());
-  fetchOwnDevices.setTo(client.configuration().jidBare());
-  fetchOwnDevices.setType(QXmppIq::Get);
-  fetchOwnDevices.setQueryNode("urn:xmpp:omemo:1:devices");
-
-  client.sendPacket(fetchOwnDevices);
+  for (const auto &device : list.devices) {
+    qInfo() << "Got device for" << jid << ":" << device.id;
+  }
 }
diff --git a/qomemo/device_service.h b/qomemo/device_service.h
index fc2e4d5..9d87efb 100644
--- a/qomemo/device_service.h
+++ b/qomemo/device_service.h
@@ -4,6 +4,7 @@
 
 #pragma once
 
+#include "qomemo.h"
 #include "user_device_list.h"
 
 #include <QXmppClient.h>
@@ -14,17 +15,12 @@ class DeviceService : public QObject {
   Q_OBJECT
 
 public:
-  DeviceService(QXmppClient& client, QObject *parent);
-
-  void fetch();
+  explicit DeviceService(QObject *parent);
 
 public slots:
-  void onIqReceived(const QXmppIq& iq);
+  void onDeviceListReceived(const QString& jid, const DeviceList& list);
 
 private:
-  void announce();
-
-  QXmppClient& client;
   QMap<QString, UserDeviceList> device_lists{};
 };
 
diff --git a/qomemo/qomemo.cpp b/qomemo/qomemo.cpp
index 9d4266a..ab863f2 100644
--- a/qomemo/qomemo.cpp
+++ b/qomemo/qomemo.cpp
@@ -13,7 +13,7 @@
 using namespace QXmpp::Factories;
 
 QXmppElement QXmpp::Omemo::DeviceList::toXml() const {
-  auto element = createElement("devices", "urn:xmpp:omemo:1");
+  auto element = createElement("list", "eu.siacs.conversations.axolotl");
 
   for (const auto &d : devices) {
     element.appendChild(d.toXml());
@@ -28,11 +28,10 @@ QXmppIq QXmpp::Omemo::DeviceList::toIq() const {
   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.setAttribute("node", "eu.siacs.conversations.axolotl.devicelist");
   publish.appendChild(item);
 
   auto pubSub = createElement("pubsub", "http://jabber.org/protocol/pubsub");
@@ -45,7 +44,7 @@ QXmppIq QXmpp::Omemo::DeviceList::toIq() const {
 }
 
 void QXmpp::Omemo::DeviceList::fromXml(const QXmppElement &element) {
-  if (!elementMatches(element, "devices", "urn:xmpp:omemo:1"))
+  if (!elementMatches(element, "list", "eu.siacs.conversations.axolotl"))
     return;
 
   devices.clear();
@@ -64,10 +63,6 @@ QXmppElement QXmpp::Omemo::Device::toXml() const {
   auto result = createElement("device");
   result.setAttribute("id", QString::number(id));
 
-  if (!label.isEmpty()) {
-    result.setAttribute("label", label);
-  }
-
   return result;
 }
 
@@ -76,12 +71,11 @@ void QXmpp::Omemo::Device::fromXml(const QXmppElement &element) {
     return;
 
   id = element.attribute("id").toInt();
-  label = element.attribute("label");
 }
 
 QXmppElement QXmpp::Omemo::PreKey::toXml() const {
-  auto pk = createElement("pk");
-  pk.setAttribute("id", QString::number(id));
+  auto pk = createElement("preKeyPublic");
+  pk.setAttribute("preKeyId", QString::number(id));
   // TODO: Base64
   pk.setValue(data);
 
@@ -89,23 +83,23 @@ QXmppElement QXmpp::Omemo::PreKey::toXml() const {
 }
 
 void QXmpp::Omemo::PreKey::fromXml(const QXmppElement &element) {
-  if (!elementMatches(element, "pk"))
+  if (!elementMatches(element, "preKeyPublic"))
     return;
 
-  id = element.attribute("id").toInt();
+  id = element.attribute("preKeyId").toInt();
   // TODO: Base64
   data = element.value();
 }
 
 QXmppElement QXmpp::Omemo::Bundle::toXml() const {
-  auto spkNode = createElement("spk");
-  spkNode.setAttribute("id", QString::number(spkId));
+  auto spkNode = createElement("signedPreKeyPublic");
+  spkNode.setAttribute("signedPreKeyId", QString::number(spkId));
   spkNode.setValue(spk);
 
-  auto spksNode = createElement("spks");
+  auto spksNode = createElement("signedPreKeySignature");
   spksNode.setValue(spks);
 
-  auto ikNode = createElement("ik");
+  auto ikNode = createElement("identityKey");
   ikNode.setValue(ik);
 
   auto prekeysNode = createElement("prekeys");
@@ -113,7 +107,7 @@ QXmppElement QXmpp::Omemo::Bundle::toXml() const {
     prekeysNode.appendChild(pk.toXml());
   }
 
-  auto result = createElement("bundle", "urn:xmpp:omemo:1");
+  auto result = createElement("bundle", "eu.siacs.conversations.axolotl");
   result.appendChild(spkNode);
   result.appendChild(spksNode);
   result.appendChild(ikNode);
@@ -122,22 +116,21 @@ QXmppElement QXmpp::Omemo::Bundle::toXml() const {
   return result;
 }
 
-QXmppIq QXmpp::Omemo::Bundle::toIq() const {
+QXmppIq QXmpp::Omemo::Bundle::toIq(int deviceId) 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.setAttribute("node", QString("eu.siacs.conversations.axolotl.bundles:%s").arg(deviceId));
   publish.appendChild(item);
 
   auto pubSub = createElement("pubsub", "http://jabber.org/protocol/pubsub");
   pubSub.appendChild(publish);
-  pubSub.appendChild(createOpenPublishOptions("max"));
+  pubSub.appendChild(createOpenPublishOptions());
 
   iq.extensions().push_back(pubSub);
 
@@ -145,7 +138,7 @@ QXmppIq QXmpp::Omemo::Bundle::toIq() const {
 }
 
 void QXmpp::Omemo::Bundle::fromXml(const QXmppElement &element) {
-  if (!elementMatches(element, "bundle", "urn:xmpp:omemo:1"))
+  if (!elementMatches(element, "bundle", "eu.siacs.conversations.axolotl"))
     return;
 
   auto spkNode = element.firstChildElement("spk");
@@ -184,7 +177,7 @@ void QXmpp::Omemo::Bundle::fromXml(const QXmppElement &element) {
 QXmppPubSubIq QXmpp::Omemo::Bundle::fetchDeviceBundleIq(int deviceId) {
   QXmppPubSubIq iq{};
   iq.setType(QXmppIq::Get);
-  iq.setQueryNode("urn:xmpp:omemo:1:bundles");
+  iq.setQueryNode(QString("eu.siacs.conversations.axolotl.bundles:%1").arg(deviceId));
 
   QXmppPubSubItem item{};
   item.setId(QString::number(deviceId));
@@ -197,13 +190,8 @@ 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());
+  auto ivNode = createElement("iv");
+  ivNode.setValue(iv);
 
   for (const auto &key : keys) {
     result.appendChild(key.toXml());
@@ -212,6 +200,16 @@ QXmppElement QXmpp::Omemo::EncryptedMessage::toXml() const {
   return result;
 }
 
+QXmppElement QXmpp::Omemo::EncryptedMessage::toXml() const {
+  auto result = createElement("encrypted", "eu.siacs.conversations.axolotl");
+
+  result.appendChild(header());
+  // TODO: Payload is optional
+  result.appendChild(payload());
+
+  return result;
+}
+
 QXmppElement QXmpp::Omemo::EncryptedMessage::payload() const {
   QBuffer buffer;
   buffer.open(QIODevice::ReadWrite);
@@ -260,21 +258,11 @@ QXmppElement QXmpp::Omemo::EncryptedMessage::content() const {
 QXmppElement QXmpp::Omemo::MessageKey::toXml() const {
   auto result = createElement("key");
 
-  if (kex)
-    result.setAttribute("kex", "true");
+  result.setAttribute("rid", QString::number(receivingDeviceId));
+  if (prekey)
+    result.setAttribute("prekey", "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;
-}
diff --git a/qomemo/qomemo.h b/qomemo/qomemo.h
index e815d11..346cc90 100644
--- a/qomemo/qomemo.h
+++ b/qomemo/qomemo.h
@@ -17,7 +17,6 @@ public:
   void fromXml(const QXmppElement &element);
 
   int id;
-  QString label;
 };
 
 class DeviceList {
@@ -27,7 +26,6 @@ public:
   /// Expects a urn:xmpp:omemo:1:devices node
   void fromXml(const QXmppElement &element);
 
-private:
   QList<Device> devices;
 };
 
@@ -46,11 +44,9 @@ public:
   [[nodiscard]] static QXmppPubSubIq fetchDeviceBundleIq(int deviceId);
 
   [[nodiscard]] QXmppElement toXml() const;
-  [[nodiscard]] QXmppIq toIq() const;
+  [[nodiscard]] QXmppIq toIq(int deviceId) const;
   void fromXml(const QXmppElement &element);
 
-  int deviceId;
-
   QString spk;
   int spkId;
   QString spks;
@@ -62,18 +58,11 @@ class MessageKey {
 public:
   [[nodiscard]] QXmppElement toXml() const;
 
-  bool kex{};
+  int receivingDeviceId{};
+  bool prekey{};
   QString key{};
 };
 
-class HeaderKeys {
-public:
-  [[nodiscard]] QXmppElement toXml() const;
-
-  QString jid{};
-  QList<MessageKey> keys{};
-};
-
 class EncryptedMessage {
 public:
   [[nodiscard]] QXmppElement header() const;
@@ -83,11 +72,13 @@ public:
 
   int fromDeviceId{};
 
-  QList<HeaderKeys> keys{};
+  QList<MessageKey> keys{};
   QString from{};
   QString to{};
   QDateTime timestamp{};
 
+  QString iv{};
+
   QXmppMessage message{};
 };
 
diff --git a/qomemo/qxmpp_omemo_manager.cpp b/qomemo/qxmpp_omemo_manager.cpp
new file mode 100644
index 0000000..26598f7
--- /dev/null
+++ b/qomemo/qxmpp_omemo_manager.cpp
@@ -0,0 +1,66 @@
+/*
+ * Created by victoria on 2021-05-12.
+ */
+
+#include "qxmpp_omemo_manager.h"
+
+#include <QDomElement>
+
+#include <QXmppClient.h>
+#include <QXmppPubSubIq.h>
+#include <iostream>
+
+QXmpp::Omemo::Manager::Manager() : deviceService(new QXmpp::Omemo::DeviceService(this)) {
+  connect(this, &Manager::deviceListReceived, deviceService.get(), &DeviceService::onDeviceListReceived);
+}
+
+bool QXmpp::Omemo::Manager::handleStanza(const QDomElement &stanza) {
+  QString str{};
+  QTextStream info(&str);
+  stanza.save(info, 4);
+
+  std::cout << str.toStdString();
+
+  if (stanza.tagName() == "iq") {
+    if (stanza.attribute("type") == "result") {
+      auto pubsub = stanza.firstChildElement("pubsub");
+      if (!pubsub.isNull()) {
+        auto items = pubsub.firstChildElement("items");
+        if (items.attribute("node") == "eu.siacs.conversations.axolotl.devicelist") {
+          auto item = items.firstChildElement("item");
+          if (!item.isNull()) {
+            auto list = item.firstChildElement("list");
+            if (!list.isNull()) {
+              DeviceList deviceList{};
+              deviceList.fromXml(list);
+              emit deviceListReceived(stanza.attribute("from"), deviceList);
+
+              return true;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  return false;
+}
+
+void QXmpp::Omemo::Manager::setClient(QXmppClient *client) {
+  QXmppClientExtension::setClient(client);
+
+  if (!client)
+    return;
+
+  QObject::connect(client, &QXmppClient::connected, this, &Manager::fetchOwnDevices);
+}
+
+void QXmpp::Omemo::Manager::fetchOwnDevices() {
+  QXmppPubSubIq iq{};
+  iq.setFrom(client()->configuration().jid());
+  iq.setTo(client()->configuration().jidBare());
+  iq.setType(QXmppIq::Get);
+  iq.setQueryNode("eu.siacs.conversations.axolotl.devicelist");
+
+  client()->sendPacket(iq);
+}
diff --git a/qomemo/qxmpp_omemo_manager.h b/qomemo/qxmpp_omemo_manager.h
new file mode 100644
index 0000000..7a6ddf4
--- /dev/null
+++ b/qomemo/qxmpp_omemo_manager.h
@@ -0,0 +1,36 @@
+/*
+ * Created by victoria on 2021-05-12.
+ */
+
+#pragma once
+
+#include "device_service.h"
+#include "qomemo.h"
+
+#include <QXmppClientExtension.h>
+
+namespace QXmpp::Omemo {
+
+class Manager : public QXmppClientExtension {
+  Q_OBJECT;
+
+public:
+  Manager();
+  ~Manager() override = default;
+
+  bool handleStanza(const QDomElement &stanza) override;
+
+public slots:
+  void fetchOwnDevices();
+
+signals:
+  void deviceListReceived(const QString& jid, const DeviceList& list);
+
+protected:
+  void setClient(QXmppClient *client) override;
+
+private:
+  QScopedPointer<DeviceService> deviceService;
+};
+
+} // namespace QXmpp::Omemo

From 12ffe8e8e6f9edd9bb6cbcc74eedb1ce59007c69 Mon Sep 17 00:00:00 2001
From: vae <vae@programming.socks.town>
Date: Thu, 13 May 2021 00:32:13 +0300
Subject: [PATCH 13/20] ref(omemo): add qomemo/variant

---
 qomemo/CMakeLists.txt            |   8 +-
 qomemo/bundle.cpp                | 126 ++++++++++++++++++++++
 qomemo/bundle.h                  |  41 ++++++++
 qomemo/device.cpp                |   5 +
 qomemo/device.h                  |  24 +++++
 qomemo/device_service.cpp        |   1 +
 qomemo/device_service.h          |   2 +
 qomemo/qomemo.cpp                | 174 -------------------------------
 qomemo/qomemo.h                  |  43 --------
 qomemo/qxmpp_omemo_manager.cpp   |  10 +-
 qomemo/qxmpp_omemo_manager.h     |   4 +-
 qomemo/variant/CMakeLists.txt    |   1 +
 qomemo/variant/conversations.cpp |  78 ++++++++++++++
 qomemo/variant/conversations.h   |  23 ++++
 qomemo/variant/omemo_base.cpp    |   5 +
 qomemo/variant/omemo_base.h      |  31 ++++++
 qomemo/variant/xep0384.cpp       |   5 +
 qomemo/variant/xep0384.h         |   7 ++
 18 files changed, 366 insertions(+), 222 deletions(-)
 create mode 100644 qomemo/bundle.cpp
 create mode 100644 qomemo/bundle.h
 create mode 100644 qomemo/device.cpp
 create mode 100644 qomemo/device.h
 create mode 100644 qomemo/variant/CMakeLists.txt
 create mode 100644 qomemo/variant/conversations.cpp
 create mode 100644 qomemo/variant/conversations.h
 create mode 100644 qomemo/variant/omemo_base.cpp
 create mode 100644 qomemo/variant/omemo_base.h
 create mode 100644 qomemo/variant/xep0384.cpp
 create mode 100644 qomemo/variant/xep0384.h

diff --git a/qomemo/CMakeLists.txt b/qomemo/CMakeLists.txt
index 1512264..95fca3f 100644
--- a/qomemo/CMakeLists.txt
+++ b/qomemo/CMakeLists.txt
@@ -1,4 +1,8 @@
 target_sources(squawk PRIVATE
+  bundle.cpp
+  bundle.h
+  device.cpp
+  device.h
   device_key_storage.cpp
   device_key_storage.h
   device_service.cpp
@@ -13,4 +17,6 @@ target_sources(squawk PRIVATE
   user_device_list.h
   qxmpp_omemo_manager.cpp
   qxmpp_omemo_manager.h
-  )
\ No newline at end of file
+  )
+
+add_subdirectory(variant)
diff --git a/qomemo/bundle.cpp b/qomemo/bundle.cpp
new file mode 100644
index 0000000..aec605a
--- /dev/null
+++ b/qomemo/bundle.cpp
@@ -0,0 +1,126 @@
+/*
+ * Created by victoria on 2021-05-12.
+ */
+
+#include "bundle.h"
+
+#include <QXmppPubSubIq.h>
+
+#include <QDebug>
+
+#include "shared/qxmppfactories.h"
+
+using namespace QXmpp::Factories;
+
+QXmppElement QXmpp::Omemo::PreKey::toXml() const {
+  auto pk = createElement("preKeyPublic");
+  pk.setAttribute("preKeyId", QString::number(id));
+  // TODO: Base64
+  pk.setValue(data);
+
+  return pk;
+}
+
+void QXmpp::Omemo::PreKey::fromXml(const QXmppElement &element) {
+  if (!elementMatches(element, "preKeyPublic"))
+    return;
+
+  id = element.attribute("preKeyId").toInt();
+  // TODO: Base64
+  data = element.value();
+}
+
+QXmppElement QXmpp::Omemo::Bundle::toXml() const {
+  auto spkNode = createElement("signedPreKeyPublic");
+  spkNode.setAttribute("signedPreKeyId", QString::number(spkId));
+  spkNode.setValue(spk);
+
+  auto spksNode = createElement("signedPreKeySignature");
+  spksNode.setValue(spks);
+
+  auto ikNode = createElement("identityKey");
+  ikNode.setValue(ik);
+
+  auto prekeysNode = createElement("prekeys");
+  for (const auto &pk : prekeys) {
+    prekeysNode.appendChild(pk.toXml());
+  }
+
+  auto result = createElement("bundle", "eu.siacs.conversations.axolotl");
+  result.appendChild(spkNode);
+  result.appendChild(spksNode);
+  result.appendChild(ikNode);
+  result.appendChild(prekeysNode);
+
+  return result;
+}
+
+QXmppIq QXmpp::Omemo::Bundle::toIq(int deviceId) const {
+  QXmppIq iq{};
+
+  iq.setType(QXmppIq::Set);
+
+  auto item = createElement("item");
+  item.appendChild(toXml());
+
+  auto publish = createElement("publish");
+  publish.setAttribute("node", QString("eu.siacs.conversations.axolotl.bundles:%s").arg(deviceId));
+  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 QXmpp::Omemo::Bundle::fromXml(const QXmppElement &element) {
+  if (!elementMatches(element, "bundle", "eu.siacs.conversations.axolotl"))
+    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);
+  }
+}
+
+QXmppPubSubIq QXmpp::Omemo::Bundle::fetchDeviceBundleIq(int deviceId) {
+  QXmppPubSubIq iq{};
+  iq.setType(QXmppIq::Get);
+  iq.setQueryNode(QString("eu.siacs.conversations.axolotl.bundles:%1").arg(deviceId));
+
+  QXmppPubSubItem item{};
+  item.setId(QString::number(deviceId));
+  iq.setItems({item});
+
+  return iq;
+}
\ No newline at end of file
diff --git a/qomemo/bundle.h b/qomemo/bundle.h
new file mode 100644
index 0000000..d29bf02
--- /dev/null
+++ b/qomemo/bundle.h
@@ -0,0 +1,41 @@
+/*
+ * Created by victoria on 2021-05-12.
+ */
+
+#pragma once
+
+#include <QList>
+#include <QString>
+
+class QXmppPubSubIq;
+class QXmppElement;
+class QXmppIq;
+
+namespace QXmpp::Omemo {
+
+class PreKey {
+public:
+  [[nodiscard]] QXmppElement toXml() const;
+  /// Expects a <pk>
+  void fromXml(const QXmppElement &element);
+
+  int id;
+  QString data;
+};
+
+class Bundle {
+public:
+  [[nodiscard]] static QXmppPubSubIq fetchDeviceBundleIq(int deviceId);
+
+  [[nodiscard]] QXmppElement toXml() const;
+  [[nodiscard]] QXmppIq toIq(int deviceId) const;
+  void fromXml(const QXmppElement &element);
+
+  QString spk;
+  int spkId;
+  QString spks;
+  QString ik;
+  QList<PreKey> prekeys;
+};
+
+}
diff --git a/qomemo/device.cpp b/qomemo/device.cpp
new file mode 100644
index 0000000..edfee1c
--- /dev/null
+++ b/qomemo/device.cpp
@@ -0,0 +1,5 @@
+/*
+ * Created by victoria on 2021-05-13.
+ */
+
+#include "device.h"
diff --git a/qomemo/device.h b/qomemo/device.h
new file mode 100644
index 0000000..4af6ada
--- /dev/null
+++ b/qomemo/device.h
@@ -0,0 +1,24 @@
+/*
+ * Created by victoria on 2021-05-13.
+ */
+
+#pragma once
+
+#include <QList>
+
+class QXmppElement;
+class QXmppIq;
+
+namespace QXmpp::Omemo {
+
+class Device {
+public:
+  int id;
+};
+
+class DeviceList {
+public:
+  QList<Device> devices;
+};
+
+} // namespace QXmpp::Omemo
diff --git a/qomemo/device_service.cpp b/qomemo/device_service.cpp
index 5e9a460..fcc6b72 100644
--- a/qomemo/device_service.cpp
+++ b/qomemo/device_service.cpp
@@ -3,6 +3,7 @@
  */
 
 #include "device_service.h"
+#include "device.h"
 
 QXmpp::Omemo::DeviceService::DeviceService(QObject *parent) : QObject(parent) {}
 
diff --git a/qomemo/device_service.h b/qomemo/device_service.h
index 9d87efb..774b7f4 100644
--- a/qomemo/device_service.h
+++ b/qomemo/device_service.h
@@ -11,6 +11,8 @@
 
 namespace QXmpp::Omemo {
 
+class DeviceList;
+
 class DeviceService : public QObject {
   Q_OBJECT
 
diff --git a/qomemo/qomemo.cpp b/qomemo/qomemo.cpp
index ab863f2..d078f37 100644
--- a/qomemo/qomemo.cpp
+++ b/qomemo/qomemo.cpp
@@ -12,180 +12,6 @@
 
 using namespace QXmpp::Factories;
 
-QXmppElement QXmpp::Omemo::DeviceList::toXml() const {
-  auto element = createElement("list", "eu.siacs.conversations.axolotl");
-
-  for (const auto &d : devices) {
-    element.appendChild(d.toXml());
-  }
-
-  return element;
-}
-
-QXmppIq QXmpp::Omemo::DeviceList::toIq() const {
-  QXmppIq iq{};
-
-  iq.setType(QXmppIq::Set);
-
-  auto item = createElement("item");
-  item.appendChild(toXml());
-
-  auto publish = createElement("publish");
-  publish.setAttribute("node", "eu.siacs.conversations.axolotl.devicelist");
-  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 QXmpp::Omemo::DeviceList::fromXml(const QXmppElement &element) {
-  if (!elementMatches(element, "list", "eu.siacs.conversations.axolotl"))
-    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 QXmpp::Omemo::Device::toXml() const {
-  auto result = createElement("device");
-  result.setAttribute("id", QString::number(id));
-
-  return result;
-}
-
-void QXmpp::Omemo::Device::fromXml(const QXmppElement &element) {
-  if (!elementMatches(element, "device"))
-    return;
-
-  id = element.attribute("id").toInt();
-}
-
-QXmppElement QXmpp::Omemo::PreKey::toXml() const {
-  auto pk = createElement("preKeyPublic");
-  pk.setAttribute("preKeyId", QString::number(id));
-  // TODO: Base64
-  pk.setValue(data);
-
-  return pk;
-}
-
-void QXmpp::Omemo::PreKey::fromXml(const QXmppElement &element) {
-  if (!elementMatches(element, "preKeyPublic"))
-    return;
-
-  id = element.attribute("preKeyId").toInt();
-  // TODO: Base64
-  data = element.value();
-}
-
-QXmppElement QXmpp::Omemo::Bundle::toXml() const {
-  auto spkNode = createElement("signedPreKeyPublic");
-  spkNode.setAttribute("signedPreKeyId", QString::number(spkId));
-  spkNode.setValue(spk);
-
-  auto spksNode = createElement("signedPreKeySignature");
-  spksNode.setValue(spks);
-
-  auto ikNode = createElement("identityKey");
-  ikNode.setValue(ik);
-
-  auto prekeysNode = createElement("prekeys");
-  for (const auto &pk : prekeys) {
-    prekeysNode.appendChild(pk.toXml());
-  }
-
-  auto result = createElement("bundle", "eu.siacs.conversations.axolotl");
-  result.appendChild(spkNode);
-  result.appendChild(spksNode);
-  result.appendChild(ikNode);
-  result.appendChild(prekeysNode);
-
-  return result;
-}
-
-QXmppIq QXmpp::Omemo::Bundle::toIq(int deviceId) const {
-  QXmppIq iq{};
-
-  iq.setType(QXmppIq::Set);
-
-  auto item = createElement("item");
-  item.appendChild(toXml());
-
-  auto publish = createElement("publish");
-  publish.setAttribute("node", QString("eu.siacs.conversations.axolotl.bundles:%s").arg(deviceId));
-  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 QXmpp::Omemo::Bundle::fromXml(const QXmppElement &element) {
-  if (!elementMatches(element, "bundle", "eu.siacs.conversations.axolotl"))
-    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);
-  }
-}
-
-QXmppPubSubIq QXmpp::Omemo::Bundle::fetchDeviceBundleIq(int deviceId) {
-  QXmppPubSubIq iq{};
-  iq.setType(QXmppIq::Get);
-  iq.setQueryNode(QString("eu.siacs.conversations.axolotl.bundles:%1").arg(deviceId));
-
-  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));
diff --git a/qomemo/qomemo.h b/qomemo/qomemo.h
index 346cc90..b79ef1b 100644
--- a/qomemo/qomemo.h
+++ b/qomemo/qomemo.h
@@ -11,49 +11,6 @@
 
 namespace QXmpp::Omemo {
 
-class Device {
-public:
-  [[nodiscard]] QXmppElement toXml() const;
-  void fromXml(const QXmppElement &element);
-
-  int id;
-};
-
-class DeviceList {
-public:
-  [[nodiscard]] QXmppElement toXml() const;
-  [[nodiscard]] QXmppIq toIq() const;
-  /// Expects a urn:xmpp:omemo:1:devices node
-  void fromXml(const QXmppElement &element);
-
-  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]] static QXmppPubSubIq fetchDeviceBundleIq(int deviceId);
-
-  [[nodiscard]] QXmppElement toXml() const;
-  [[nodiscard]] QXmppIq toIq(int deviceId) const;
-  void fromXml(const QXmppElement &element);
-
-  QString spk;
-  int spkId;
-  QString spks;
-  QString ik;
-  QList<PreKey> prekeys;
-};
-
 class MessageKey {
 public:
   [[nodiscard]] QXmppElement toXml() const;
diff --git a/qomemo/qxmpp_omemo_manager.cpp b/qomemo/qxmpp_omemo_manager.cpp
index 26598f7..f29ec98 100644
--- a/qomemo/qxmpp_omemo_manager.cpp
+++ b/qomemo/qxmpp_omemo_manager.cpp
@@ -4,13 +4,18 @@
 
 #include "qxmpp_omemo_manager.h"
 
+#include "device.h"
+#include "variant/conversations.h"
+
 #include <QDomElement>
 
 #include <QXmppClient.h>
 #include <QXmppPubSubIq.h>
 #include <iostream>
 
-QXmpp::Omemo::Manager::Manager() : deviceService(new QXmpp::Omemo::DeviceService(this)) {
+using namespace QXmpp::Omemo;
+
+Manager::Manager() : deviceService(new DeviceService(this)), omemoVariant(new Variant::Conversations) {
   connect(this, &Manager::deviceListReceived, deviceService.get(), &DeviceService::onDeviceListReceived);
 }
 
@@ -31,8 +36,7 @@ bool QXmpp::Omemo::Manager::handleStanza(const QDomElement &stanza) {
           if (!item.isNull()) {
             auto list = item.firstChildElement("list");
             if (!list.isNull()) {
-              DeviceList deviceList{};
-              deviceList.fromXml(list);
+              DeviceList deviceList = omemoVariant->deviceListFromXml(list);
               emit deviceListReceived(stanza.attribute("from"), deviceList);
 
               return true;
diff --git a/qomemo/qxmpp_omemo_manager.h b/qomemo/qxmpp_omemo_manager.h
index 7a6ddf4..377c77d 100644
--- a/qomemo/qxmpp_omemo_manager.h
+++ b/qomemo/qxmpp_omemo_manager.h
@@ -6,6 +6,7 @@
 
 #include "device_service.h"
 #include "qomemo.h"
+#include "variant/omemo_base.h"
 
 #include <QXmppClientExtension.h>
 
@@ -24,13 +25,14 @@ public slots:
   void fetchOwnDevices();
 
 signals:
-  void deviceListReceived(const QString& jid, const DeviceList& list);
+  void deviceListReceived(const QString &jid, const DeviceList &list);
 
 protected:
   void setClient(QXmppClient *client) override;
 
 private:
   QScopedPointer<DeviceService> deviceService;
+  QScopedPointer<Variant::Base> omemoVariant;
 };
 
 } // namespace QXmpp::Omemo
diff --git a/qomemo/variant/CMakeLists.txt b/qomemo/variant/CMakeLists.txt
new file mode 100644
index 0000000..a2b211a
--- /dev/null
+++ b/qomemo/variant/CMakeLists.txt
@@ -0,0 +1 @@
+target_sources(squawk PRIVATE xep0384.cpp xep0384.h conversations.cpp conversations.h omemo_base.cpp omemo_base.h)
diff --git a/qomemo/variant/conversations.cpp b/qomemo/variant/conversations.cpp
new file mode 100644
index 0000000..b2f85c4
--- /dev/null
+++ b/qomemo/variant/conversations.cpp
@@ -0,0 +1,78 @@
+/*
+ * Created by victoria on 2021-05-13.
+ */
+
+#include "conversations.h"
+
+#include "qomemo/device.h"
+#include "shared/qxmppfactories.h"
+
+#include <QXmppIq.h>
+
+using namespace QXmpp::Omemo;
+using namespace QXmpp::Factories;
+
+QXmppElement Variant::Conversations::deviceToXml(const Device &device) {
+  auto result = createElement("device");
+  result.setAttribute("id", QString::number(device.id));
+
+  return result;
+}
+
+Device Variant::Conversations::deviceFromXml(const QXmppElement &xml) {
+  Device result{};
+
+  if (!elementMatches(xml, "device"))
+    return result;
+
+  result.id = xml.attribute("id").toInt();
+
+  return result;
+}
+
+QXmppElement
+Variant::Conversations::deviceListToXml(const DeviceList &deviceList) {
+  auto element = createElement("list", "eu.siacs.conversations.axolotl");
+
+  for (const auto &device : deviceList.devices) {
+    element.appendChild(deviceToXml(device));
+  }
+
+  return element;
+}
+
+DeviceList Variant::Conversations::deviceListFromXml(const QXmppElement &xml) {
+  DeviceList result{};
+
+  if (!elementMatches(xml, "list", "eu.siacs.conversations.axolotl"))
+    return result;
+
+  auto deviceElement = xml.firstChildElement("device");
+  while (!deviceElement.isNull()) {
+    result.devices.push_back(deviceFromXml(deviceElement));
+    deviceElement = deviceElement.nextSiblingElement("device");
+  }
+
+  return result;
+}
+
+QXmppIq Variant::Conversations::deviceListSetIq(const DeviceList &deviceList) {
+  QXmppIq iq{};
+
+  iq.setType(QXmppIq::Set);
+
+  auto item = createElement("item");
+  item.appendChild(deviceListToXml(deviceList));
+
+  auto publish = createElement("publish");
+  publish.setAttribute("node", "eu.siacs.conversations.axolotl.devicelist");
+  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;
+}
diff --git a/qomemo/variant/conversations.h b/qomemo/variant/conversations.h
new file mode 100644
index 0000000..917dbcd
--- /dev/null
+++ b/qomemo/variant/conversations.h
@@ -0,0 +1,23 @@
+/*
+ * Created by victoria on 2021-05-13.
+ */
+
+#pragma once
+
+#include "omemo_base.h"
+
+namespace QXmpp::Omemo::Variant {
+
+class Conversations : public Base {
+public:
+  ~Conversations() override = default;
+
+  QXmppElement deviceToXml(const Device &device) override;
+  Device deviceFromXml(const QXmppElement &xml) override;
+
+  QXmppElement deviceListToXml(const DeviceList &deviceList) override;
+  DeviceList deviceListFromXml(const QXmppElement &xml) override;
+  QXmppIq deviceListSetIq(const DeviceList &deviceList) override;
+};
+
+} // namespace QXmpp::Omemo::Variant
diff --git a/qomemo/variant/omemo_base.cpp b/qomemo/variant/omemo_base.cpp
new file mode 100644
index 0000000..a3e8493
--- /dev/null
+++ b/qomemo/variant/omemo_base.cpp
@@ -0,0 +1,5 @@
+/*
+ * Created by victoria on 2021-05-13.
+ */
+
+#include "omemo_base.h"
diff --git a/qomemo/variant/omemo_base.h b/qomemo/variant/omemo_base.h
new file mode 100644
index 0000000..f383a59
--- /dev/null
+++ b/qomemo/variant/omemo_base.h
@@ -0,0 +1,31 @@
+/*
+ * Created by victoria on 2021-05-13.
+ */
+
+#pragma once
+
+class QXmppElement;
+class QXmppIq;
+
+namespace QXmpp::Omemo {
+
+class Device;
+class DeviceList;
+
+namespace Variant {
+
+class Base {
+public:
+  virtual ~Base() = default;
+
+  virtual QXmppElement deviceToXml(const Device& device) = 0;
+  virtual Device deviceFromXml(const QXmppElement& xml) = 0;
+
+  virtual QXmppElement deviceListToXml(const DeviceList& deviceList) = 0;
+  virtual DeviceList deviceListFromXml(const QXmppElement& xml) = 0;
+  virtual QXmppIq deviceListSetIq(const DeviceList& deviceList) = 0;
+};
+
+} // namespace Variant
+
+} // namespace QXmpp::Omemo
diff --git a/qomemo/variant/xep0384.cpp b/qomemo/variant/xep0384.cpp
new file mode 100644
index 0000000..8ec2597
--- /dev/null
+++ b/qomemo/variant/xep0384.cpp
@@ -0,0 +1,5 @@
+/*
+ * Created by victoria on 2021-05-13.
+ */
+
+#include "xep0384.h"
diff --git a/qomemo/variant/xep0384.h b/qomemo/variant/xep0384.h
new file mode 100644
index 0000000..9298b24
--- /dev/null
+++ b/qomemo/variant/xep0384.h
@@ -0,0 +1,7 @@
+/*
+ * Created by victoria on 2021-05-13.
+ */
+
+#pragma once
+
+namespace QXmpp::Omemo {}

From 2654e386657fcd5d6b1e568587598c60c38e8741 Mon Sep 17 00:00:00 2001
From: vae <vae@programming.socks.town>
Date: Thu, 13 May 2021 17:50:29 +0300
Subject: [PATCH 14/20] feat(omemo): add signal protocol wrappers

---
 qomemo/CMakeLists.txt                         |   5 +-
 qomemo/signal.cpp                             |   5 -
 qomemo/signal.h                               |  20 --
 qomemo/signal/CMakeLists.txt                  |   7 +
 qomemo/signal/context.cpp                     |   9 +
 qomemo/signal/context.h                       |  23 +++
 qomemo/signal/crypto/CMakeLists.txt           |  10 +
 qomemo/signal/crypto/aes_openssl.cpp          | 187 ++++++++++++++++++
 qomemo/signal/crypto/aes_openssl.h            |  17 ++
 qomemo/signal/crypto/crypto.cpp               |  40 ++++
 qomemo/signal/crypto/crypto.h                 |  13 ++
 qomemo/signal/crypto/hmac_sha256_openssl.cpp  |  71 +++++++
 qomemo/signal/crypto/hmac_sha256_openssl.h    |  16 ++
 .../signal/crypto/sha512_digest_openssl.cpp   |  67 +++++++
 qomemo/signal/crypto/sha512_digest_openssl.h  |  16 ++
 qomemo/signal/stores/CMakeLists.txt           |  14 ++
 qomemo/signal/stores/identity_key_store.cpp   |  47 +++++
 qomemo/signal/stores/identity_key_store.h     |  21 ++
 qomemo/signal/stores/pre_key_store.cpp        |  43 ++++
 qomemo/signal/stores/pre_key_store.h          |  21 ++
 qomemo/signal/stores/sender_key_store.cpp     |  36 ++++
 qomemo/signal/stores/sender_key_store.h       |  21 ++
 qomemo/signal/stores/session_store.cpp        |  63 ++++++
 qomemo/signal/stores/session_store.h          |  24 +++
 qomemo/signal/stores/signed_pre_key_store.cpp |  48 +++++
 qomemo/signal/stores/signed_pre_key_store.h   |  20 ++
 qomemo/signal/stores/store_context.cpp        |  13 ++
 qomemo/signal/stores/store_context.h          |  24 +++
 28 files changed, 874 insertions(+), 27 deletions(-)
 delete mode 100644 qomemo/signal.cpp
 delete mode 100644 qomemo/signal.h
 create mode 100644 qomemo/signal/CMakeLists.txt
 create mode 100644 qomemo/signal/context.cpp
 create mode 100644 qomemo/signal/context.h
 create mode 100644 qomemo/signal/crypto/CMakeLists.txt
 create mode 100644 qomemo/signal/crypto/aes_openssl.cpp
 create mode 100644 qomemo/signal/crypto/aes_openssl.h
 create mode 100644 qomemo/signal/crypto/crypto.cpp
 create mode 100644 qomemo/signal/crypto/crypto.h
 create mode 100644 qomemo/signal/crypto/hmac_sha256_openssl.cpp
 create mode 100644 qomemo/signal/crypto/hmac_sha256_openssl.h
 create mode 100644 qomemo/signal/crypto/sha512_digest_openssl.cpp
 create mode 100644 qomemo/signal/crypto/sha512_digest_openssl.h
 create mode 100644 qomemo/signal/stores/CMakeLists.txt
 create mode 100644 qomemo/signal/stores/identity_key_store.cpp
 create mode 100644 qomemo/signal/stores/identity_key_store.h
 create mode 100644 qomemo/signal/stores/pre_key_store.cpp
 create mode 100644 qomemo/signal/stores/pre_key_store.h
 create mode 100644 qomemo/signal/stores/sender_key_store.cpp
 create mode 100644 qomemo/signal/stores/sender_key_store.h
 create mode 100644 qomemo/signal/stores/session_store.cpp
 create mode 100644 qomemo/signal/stores/session_store.h
 create mode 100644 qomemo/signal/stores/signed_pre_key_store.cpp
 create mode 100644 qomemo/signal/stores/signed_pre_key_store.h
 create mode 100644 qomemo/signal/stores/store_context.cpp
 create mode 100644 qomemo/signal/stores/store_context.h

diff --git a/qomemo/CMakeLists.txt b/qomemo/CMakeLists.txt
index 95fca3f..7391404 100644
--- a/qomemo/CMakeLists.txt
+++ b/qomemo/CMakeLists.txt
@@ -1,14 +1,14 @@
 target_sources(squawk PRIVATE
   bundle.cpp
   bundle.h
+  database.cpp
+  database.h
   device.cpp
   device.h
   device_key_storage.cpp
   device_key_storage.h
   device_service.cpp
   device_service.h
-  signal.h
-  signal.cpp
   qomemo.cpp
   qomemo.h
   sce.cpp
@@ -19,4 +19,5 @@ target_sources(squawk PRIVATE
   qxmpp_omemo_manager.h
   )
 
+add_subdirectory(signal)
 add_subdirectory(variant)
diff --git a/qomemo/signal.cpp b/qomemo/signal.cpp
deleted file mode 100644
index 0e5257b..0000000
--- a/qomemo/signal.cpp
+++ /dev/null
@@ -1,5 +0,0 @@
-/*
- * Created by victoria on 2021-05-11.
- */
-
-#include "signal.h"
diff --git a/qomemo/signal.h b/qomemo/signal.h
deleted file mode 100644
index 4aa5701..0000000
--- a/qomemo/signal.h
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Created by victoria on 2021-05-11.
- */
-
-#pragma once
-
-#include <signal/signal_protocol.h>
-
-namespace Signal
-{
-
-class Context {};
-class RatchetIdentityPair {};
-class SessionSignedPreKey {};
-class ProtocolKeyHelper {};
-class ProtocolStoreContext {};
-class SessionBuilder {};
-class SessionCipher {};
-
-}
diff --git a/qomemo/signal/CMakeLists.txt b/qomemo/signal/CMakeLists.txt
new file mode 100644
index 0000000..60cfdd8
--- /dev/null
+++ b/qomemo/signal/CMakeLists.txt
@@ -0,0 +1,7 @@
+target_sources(squawk PRIVATE
+        context.cpp
+        context.h
+        )
+
+add_subdirectory(crypto)
+add_subdirectory(stores)
\ No newline at end of file
diff --git a/qomemo/signal/context.cpp b/qomemo/signal/context.cpp
new file mode 100644
index 0000000..bc2322e
--- /dev/null
+++ b/qomemo/signal/context.cpp
@@ -0,0 +1,9 @@
+/*
+ * Created by victoria on 2021-05-13.
+ */
+
+#include "context.h"
+
+Signal::Context::Context() {}
+
+Signal::Context::~Context() {}
diff --git a/qomemo/signal/context.h b/qomemo/signal/context.h
new file mode 100644
index 0000000..c398105
--- /dev/null
+++ b/qomemo/signal/context.h
@@ -0,0 +1,23 @@
+/*
+ * Created by victoria on 2021-05-13.
+ */
+
+#pragma once
+
+#include <signal/signal_protocol.h>
+
+namespace Signal {
+
+    class Context {
+    public:
+        Context();
+        ~Context();
+        Context(const Context &) = delete;
+        Context(Context &&) = delete;
+        Context &operator=(const Context &) = delete;
+
+    private:
+        signal_context *ctx{nullptr};
+    };
+
+} // namespace Signal
diff --git a/qomemo/signal/crypto/CMakeLists.txt b/qomemo/signal/crypto/CMakeLists.txt
new file mode 100644
index 0000000..6359510
--- /dev/null
+++ b/qomemo/signal/crypto/CMakeLists.txt
@@ -0,0 +1,10 @@
+target_sources(squawk PRIVATE
+        aes_openssl.cpp
+        aes_openssl.h
+        crypto.cpp
+        crypto.h
+        hmac_sha256_openssl.cpp
+        hmac_sha256_openssl.h
+        sha512_digest_openssl.cpp
+        sha512_digest_openssl.h
+        )
diff --git a/qomemo/signal/crypto/aes_openssl.cpp b/qomemo/signal/crypto/aes_openssl.cpp
new file mode 100644
index 0000000..17f2df8
--- /dev/null
+++ b/qomemo/signal/crypto/aes_openssl.cpp
@@ -0,0 +1,187 @@
+/*
+ * Created by victoria on 2021-05-13.
+ */
+
+#include "aes_openssl.h"
+#include <memory>
+
+extern "C" {
+#include <openssl/evp.h>
+#include <openssl/opensslv.h>
+}
+
+using namespace Signal::Crypto;
+
+class EVPCipherCtxWrapper {
+public:
+    EVPCipherCtxWrapper() {
+#if OPENSSL_VERSION_NUMBER >= 0x1010000fL
+        ctx = EVP_CIPHER_CTX_new();
+#else
+        ctx = new EVP_CIPHER_CTX;
+        EVP_CIPHER_CTX_init(ctx);
+#endif
+    }
+
+    ~EVPCipherCtxWrapper() {
+        if (good()) {
+#if OPENSSL_VERSION_NUMBER >= 0x1010000fL
+            EVP_CIPHER_CTX_free(ctx);
+#else
+            EVP_CIPHER_CTX_cleanup(ctx);
+            delete ctx;
+#endif
+        }
+    }
+
+    EVPCipherCtxWrapper(const EVPCipherCtxWrapper &) = delete;
+    EVPCipherCtxWrapper(EVPCipherCtxWrapper &&) = delete;
+    EVPCipherCtxWrapper &operator=(const EVPCipherCtxWrapper &) = delete;
+
+    [[nodiscard]] bool good() const { return ctx != nullptr; }
+
+    [[nodiscard]] EVP_CIPHER_CTX *operator*() const { return ctx; }
+
+private:
+    EVP_CIPHER_CTX *ctx{nullptr};
+};
+
+static const EVP_CIPHER *aes_cipher(int cipher, size_t key_len) {
+    if (cipher == SG_CIPHER_AES_CBC_PKCS5) {
+        if (key_len == 16) {
+            return EVP_aes_128_cbc();
+        } else if (key_len == 24) {
+            return EVP_aes_192_cbc();
+        } else if (key_len == 32) {
+            return EVP_aes_256_cbc();
+        }
+    } else if (cipher == SG_CIPHER_AES_CTR_NOPADDING) {
+        if (key_len == 16) {
+            return EVP_aes_128_ctr();
+        } else if (key_len == 24) {
+            return EVP_aes_192_ctr();
+        } else if (key_len == 32) {
+            return EVP_aes_256_ctr();
+        }
+    }
+    return nullptr;
+}
+
+int Aes::encrypt(signal_buffer **output, int cipher, const uint8_t *key, size_t key_len, const uint8_t *iv,
+                 size_t iv_len, const uint8_t *plaintext, size_t plaintext_len, void *) {
+    const EVP_CIPHER *evp_cipher = aes_cipher(cipher, key_len);
+    if (!evp_cipher) {
+        fprintf(stderr, "invalid AES mode or key size: %zu\n", key_len);
+        return SG_ERR_UNKNOWN;
+    }
+
+    if (iv_len != 16) {
+        fprintf(stderr, "invalid AES IV size: %zu\n", iv_len);
+        return SG_ERR_UNKNOWN;
+    }
+
+    if (plaintext_len > INT_MAX - EVP_CIPHER_block_size(evp_cipher)) {
+        fprintf(stderr, "invalid plaintext length: %zu\n", plaintext_len);
+        return SG_ERR_UNKNOWN;
+    }
+
+    EVPCipherCtxWrapper ctx{};
+    if (!ctx.good()) {
+        fprintf(stderr, "could not create context\n");
+        return SG_ERR_UNKNOWN;
+    }
+
+    auto result = EVP_EncryptInit_ex(*ctx, evp_cipher, nullptr, key, iv);
+    if (!result) {
+        fprintf(stderr, "cannot initialize cipher\n");
+        return SG_ERR_UNKNOWN;
+    }
+
+    if (cipher == SG_CIPHER_AES_CTR_NOPADDING) {
+        result = EVP_CIPHER_CTX_set_padding(*ctx, 0);
+        if (!result) {
+            fprintf(stderr, "cannot set padding\n");
+            return SG_ERR_UNKNOWN;
+        }
+    }
+
+    auto out_buf = std::make_unique<uint8_t>(plaintext_len + EVP_CIPHER_block_size(evp_cipher));
+
+    int out_len = 0;
+    result = EVP_EncryptUpdate(*ctx, out_buf.get(), &out_len, plaintext,
+                               plaintext_len);
+    if (!result) {
+        fprintf(stderr, "cannot encrypt plaintext\n");
+        return SG_ERR_UNKNOWN;
+    }
+
+    int final_len = 0;
+    result = EVP_EncryptFinal_ex(*ctx, out_buf.get() + out_len, &final_len);
+    if (!result) {
+        fprintf(stderr, "cannot finish encrypting plaintext\n");
+        return SG_ERR_UNKNOWN;
+    }
+
+    *output = signal_buffer_create(out_buf.get(), out_len + final_len);
+
+    return result;
+}
+
+int Aes::decrypt(signal_buffer **output, int cipher, const uint8_t *key, size_t key_len, const uint8_t *iv,
+                 size_t iv_len, const uint8_t *ciphertext, size_t ciphertext_len, void *) {
+    const EVP_CIPHER *evp_cipher = aes_cipher(cipher, key_len);
+    if (!evp_cipher) {
+        fprintf(stderr, "invalid AES mode or key size: %zu\n", key_len);
+        return SG_ERR_INVAL;
+    }
+
+    if (iv_len != 16) {
+        fprintf(stderr, "invalid AES IV size: %zu\n", iv_len);
+        return SG_ERR_INVAL;
+    }
+
+    if (ciphertext_len > INT_MAX - EVP_CIPHER_block_size(evp_cipher)) {
+        fprintf(stderr, "invalid ciphertext length: %zu\n", ciphertext_len);
+        return SG_ERR_UNKNOWN;
+    }
+
+    EVPCipherCtxWrapper ctx{};
+    if (!ctx.good()) {
+        fprintf(stderr, "could not create context\n");
+        return SG_ERR_UNKNOWN;
+    }
+
+    auto result = EVP_DecryptInit_ex(*ctx, evp_cipher, nullptr, key, iv);
+    if (!result) {
+        fprintf(stderr, "cannot initialize cipher\n");
+        return SG_ERR_UNKNOWN;
+    }
+
+    if (cipher == SG_CIPHER_AES_CTR_NOPADDING) {
+        result = EVP_CIPHER_CTX_set_padding(*ctx, 0);
+        if (!result) {
+            fprintf(stderr, "cannot set padding\n");
+            return SG_ERR_UNKNOWN;
+        }
+    }
+
+    auto out_buf = std::make_unique<uint8_t>(ciphertext_len + EVP_CIPHER_block_size(evp_cipher));
+
+    int out_len = 0;
+    result = EVP_DecryptUpdate(*ctx, out_buf.get(), &out_len, ciphertext, ciphertext_len);
+    if (!result) {
+        fprintf(stderr, "cannot decrypt ciphertext\n");
+        return SG_ERR_UNKNOWN;
+    }
+
+    int final_len = 0;
+    result = EVP_DecryptFinal_ex(*ctx, out_buf.get() + out_len, &final_len);
+    if (!result) {
+        fprintf(stderr, "cannot finish decrypting ciphertext\n");
+        return SG_ERR_UNKNOWN;
+    }
+
+    *output = signal_buffer_create(out_buf.get(), out_len + final_len);
+
+    return result;
+}
\ No newline at end of file
diff --git a/qomemo/signal/crypto/aes_openssl.h b/qomemo/signal/crypto/aes_openssl.h
new file mode 100644
index 0000000..63f4635
--- /dev/null
+++ b/qomemo/signal/crypto/aes_openssl.h
@@ -0,0 +1,17 @@
+/*
+ * Created by victoria on 2021-05-13.
+ */
+
+#pragma once
+
+#include <signal/signal_protocol.h>
+
+namespace Signal::Crypto::Aes {
+
+    int encrypt(signal_buffer **output, int cipher, const uint8_t *key, size_t key_len, const uint8_t *iv,
+                size_t iv_len, const uint8_t *plaintext, size_t plaintext_len, void *user_data);
+
+    int decrypt(signal_buffer **output, int cipher, const uint8_t *key, size_t key_len, const uint8_t *iv,
+                size_t iv_len, const uint8_t *ciphertext, size_t ciphertext_len, void *user_data);
+
+} // namespace Signal::Crypto::Aes
diff --git a/qomemo/signal/crypto/crypto.cpp b/qomemo/signal/crypto/crypto.cpp
new file mode 100644
index 0000000..d6a6058
--- /dev/null
+++ b/qomemo/signal/crypto/crypto.cpp
@@ -0,0 +1,40 @@
+/*
+ * Created by victoria on 2021-05-13.
+ */
+
+#include "crypto.h"
+
+extern "C" {
+#include <openssl/rand.h>
+}
+
+#include "aes_openssl.h"
+#include "hmac_sha256_openssl.h"
+#include "sha512_digest_openssl.h"
+
+int random_func(uint8_t *data, size_t len, void *) {
+    if (RAND_bytes(data, len)) {
+        return 0;
+    } else {
+        return SG_ERR_UNKNOWN;
+    }
+}
+
+signal_crypto_provider Signal::Crypto::createProvider() {
+    signal_crypto_provider result{};
+
+    result.random_func = random_func;
+    result.hmac_sha256_init_func = HmacSha256::init;
+    result.hmac_sha256_update_func = HmacSha256::update;
+    result.hmac_sha256_final_func = HmacSha256::final;
+    result.hmac_sha256_cleanup_func = HmacSha256::cleanup;
+    result.sha512_digest_init_func = Sha512::init;
+    result.sha512_digest_update_func = Sha512::update;
+    result.sha512_digest_final_func = Sha512::final;
+    result.sha512_digest_cleanup_func = Sha512::cleanup;
+    result.encrypt_func = Aes::encrypt;
+    result.decrypt_func = Aes::decrypt;
+    result.user_data = nullptr;
+
+    return result;
+}
diff --git a/qomemo/signal/crypto/crypto.h b/qomemo/signal/crypto/crypto.h
new file mode 100644
index 0000000..2fbff31
--- /dev/null
+++ b/qomemo/signal/crypto/crypto.h
@@ -0,0 +1,13 @@
+/*
+ * Created by victoria on 2021-05-13.
+ */
+
+#pragma once
+
+#include <signal/signal_protocol.h>
+
+namespace Signal::Crypto {
+
+    signal_crypto_provider createProvider();
+
+}
diff --git a/qomemo/signal/crypto/hmac_sha256_openssl.cpp b/qomemo/signal/crypto/hmac_sha256_openssl.cpp
new file mode 100644
index 0000000..2a8aba5
--- /dev/null
+++ b/qomemo/signal/crypto/hmac_sha256_openssl.cpp
@@ -0,0 +1,71 @@
+/*
+ * Created by victoria on 2021-05-13.
+ */
+
+#include "hmac_sha256_openssl.h"
+
+extern "C" {
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+#include <openssl/sha.h>
+}
+
+using namespace Signal::Crypto;
+
+int HmacSha256::init(void **hmac_context, const uint8_t *key, size_t key_len, void *) {
+#if OPENSSL_VERSION_NUMBER >= 0x1010000fL
+    HMAC_CTX *ctx = HMAC_CTX_new();
+    if (!ctx) {
+        return SG_ERR_NOMEM;
+    }
+#else
+    auto ctx = new HMAC_CTX;
+    HMAC_CTX_init(ctx);
+#endif
+
+    *hmac_context = ctx;
+
+    if (HMAC_Init_ex(ctx, key, key_len, EVP_sha256(), nullptr) != 1) {
+        return SG_ERR_UNKNOWN;
+    }
+
+    return SG_SUCCESS;
+}
+
+int HmacSha256::update(void *hmac_context, const uint8_t *data, size_t data_len, void *) {
+    auto ctx = static_cast<HMAC_CTX *>(hmac_context);
+    int result = HMAC_Update(ctx, data, data_len);
+
+    return (result == 1) ? SG_SUCCESS : SG_ERR_UNKNOWN;
+}
+
+int HmacSha256::final(void *hmac_context, signal_buffer **output, void *) {
+    auto ctx = static_cast<HMAC_CTX *>(hmac_context);
+    unsigned char md[EVP_MAX_MD_SIZE];
+    unsigned int len = 0;
+
+    if (HMAC_Final(ctx, md, &len) != 1) {
+        return SG_ERR_UNKNOWN;
+    }
+
+    signal_buffer *output_buffer = signal_buffer_create(md, len);
+    if (!output_buffer) {
+        return SG_ERR_NOMEM;
+    }
+
+    *output = output_buffer;
+
+    return SG_SUCCESS;
+}
+
+void HmacSha256::cleanup(void *hmac_context, void *) {
+    if (hmac_context) {
+        auto ctx = static_cast<HMAC_CTX *>(hmac_context);
+#if OPENSSL_VERSION_NUMBER >= 0x1010000fL
+        HMAC_CTX_free(ctx);
+#else
+        HMAC_CTX_cleanup(ctx);
+        delete ctx;
+#endif
+    }
+}
\ No newline at end of file
diff --git a/qomemo/signal/crypto/hmac_sha256_openssl.h b/qomemo/signal/crypto/hmac_sha256_openssl.h
new file mode 100644
index 0000000..03fe8e0
--- /dev/null
+++ b/qomemo/signal/crypto/hmac_sha256_openssl.h
@@ -0,0 +1,16 @@
+/*
+ * Created by victoria on 2021-05-13.
+ */
+
+#pragma once
+
+#include <signal/signal_protocol.h>
+
+namespace Signal::Crypto::HmacSha256 {
+
+    int init(void **hmac_context, const uint8_t *key, size_t key_len, void *);
+    int update(void *hmac_context, const uint8_t *data, size_t data_len, void *);
+    int final(void *hmac_context, signal_buffer **output, void *);
+    void cleanup(void *hmac_context, void *);
+
+} // namespace Signal::Crypto::HmacSha256
diff --git a/qomemo/signal/crypto/sha512_digest_openssl.cpp b/qomemo/signal/crypto/sha512_digest_openssl.cpp
new file mode 100644
index 0000000..360f11e
--- /dev/null
+++ b/qomemo/signal/crypto/sha512_digest_openssl.cpp
@@ -0,0 +1,67 @@
+/*
+ * Created by victoria on 2021-05-13.
+ */
+
+#include "sha512_digest_openssl.h"
+
+extern "C" {
+#include <openssl/evp.h>
+#include <openssl/rand.h>
+#include <openssl/sha.h>
+}
+
+using namespace Signal::Crypto;
+
+int Sha512::init(void **digest_context, void *) {
+    auto ctx = EVP_MD_CTX_create();
+    if (!ctx) {
+        return SG_ERR_NOMEM;
+    }
+
+    auto result = EVP_DigestInit_ex(ctx, EVP_sha512(), nullptr);
+
+    if (result == 1) {
+        *digest_context = ctx;
+        return SG_SUCCESS;
+    }
+
+    EVP_MD_CTX_destroy(ctx);
+    return SG_ERR_UNKNOWN;
+}
+
+int Sha512::update(void *digest_context, const uint8_t *data, size_t data_len, void *) {
+    auto ctx = static_cast<EVP_MD_CTX *>(digest_context);
+    auto result = EVP_DigestUpdate(ctx, data, data_len);
+
+    return (result == 1) ? SG_SUCCESS : SG_ERR_UNKNOWN;
+}
+
+int Sha512::final(void *digest_context, signal_buffer **output, void *) {
+    auto ctx = static_cast<EVP_MD_CTX *>(digest_context);
+    unsigned char md[EVP_MAX_MD_SIZE];
+    unsigned int len = 0;
+
+    auto result = EVP_DigestFinal_ex(ctx, md, &len);
+    if (result != 1) {
+        return SG_ERR_UNKNOWN;
+    }
+
+    result = EVP_DigestInit_ex(ctx, EVP_sha512(), nullptr);
+    if (result != 1) {
+        return SG_ERR_UNKNOWN;
+    }
+
+    signal_buffer *output_buffer = signal_buffer_create(md, len);
+    if (!output_buffer) {
+        return SG_ERR_NOMEM;
+    }
+
+    *output = output_buffer;
+
+    return SG_SUCCESS;
+}
+
+void Sha512::cleanup(void *digest_context, void *) {
+    auto ctx = static_cast<EVP_MD_CTX *>(digest_context);
+    EVP_MD_CTX_destroy(ctx);
+}
\ No newline at end of file
diff --git a/qomemo/signal/crypto/sha512_digest_openssl.h b/qomemo/signal/crypto/sha512_digest_openssl.h
new file mode 100644
index 0000000..8b263a2
--- /dev/null
+++ b/qomemo/signal/crypto/sha512_digest_openssl.h
@@ -0,0 +1,16 @@
+/*
+ * Created by victoria on 2021-05-13.
+ */
+
+#pragma once
+
+#include <signal/signal_protocol.h>
+
+namespace Signal::Crypto::Sha512 {
+
+    int init(void **digest_context, void *);
+    int update(void *digest_context, const uint8_t *data, size_t data_len, void *);
+    int final(void *digest_context, signal_buffer **output, void *);
+    void cleanup(void *digest_context, void *);
+
+} // namespace Signal::Crypto::Sha512
diff --git a/qomemo/signal/stores/CMakeLists.txt b/qomemo/signal/stores/CMakeLists.txt
new file mode 100644
index 0000000..971b87b
--- /dev/null
+++ b/qomemo/signal/stores/CMakeLists.txt
@@ -0,0 +1,14 @@
+target_sources(squawk PRIVATE
+        identity_key_store.cpp
+        identity_key_store.h
+        pre_key_store.cpp
+        pre_key_store.h
+        sender_key_store.cpp
+        sender_key_store.h
+        session_store.cpp
+        session_store.h
+        signed_pre_key_store.cpp
+        signed_pre_key_store.h
+        store_context.cpp
+        store_context.h
+        )
\ No newline at end of file
diff --git a/qomemo/signal/stores/identity_key_store.cpp b/qomemo/signal/stores/identity_key_store.cpp
new file mode 100644
index 0000000..a8b28df
--- /dev/null
+++ b/qomemo/signal/stores/identity_key_store.cpp
@@ -0,0 +1,47 @@
+/*
+ * Created by victoria on 2021-05-13.
+ */
+
+#include "identity_key_store.h"
+
+void Signal::Store::IdentityKeyStore::boundToContext(
+        signal_protocol_store_context *ctx) {
+    signal_protocol_identity_key_store store{};
+
+    store.user_data = nullptr;
+    store.destroy_func = nullptr;
+
+    store.get_identity_key_pair = [](signal_buffer **public_data, signal_buffer **private_data, void *ptr) {
+        return static_cast<IdentityKeyStore *>(ptr)->getIdentityKeyPair(public_data, private_data);
+    };
+    store.get_local_registration_id = [](void *ptr, uint32_t *registrationId) {
+        return static_cast<IdentityKeyStore *>(ptr)->getLocalRegistrationId(registrationId);
+    };
+    store.is_trusted_identity = [](const signal_protocol_address *address, uint8_t *key_data, size_t key_len,
+                                   void *ptr) {
+        return static_cast<IdentityKeyStore *>(ptr)->isTrustedIdentity(address, key_data, key_len);
+    };
+    store.save_identity = [](const signal_protocol_address *address, uint8_t *key_data, size_t key_len, void *ptr) {
+        return static_cast<IdentityKeyStore *>(ptr)->saveIdentity(address, key_data, key_len);
+    };
+
+    signal_protocol_store_context_set_identity_key_store(ctx, &store);
+}
+
+int Signal::Store::IdentityKeyStore::getIdentityKeyPair(signal_buffer **public_data, signal_buffer **private_data) {
+    return 0;
+}
+
+int Signal::Store::IdentityKeyStore::getLocalRegistrationId(uint32_t *registration_id) {
+    return 0;
+}
+
+int Signal::Store::IdentityKeyStore::saveIdentity(const signal_protocol_address *address, uint8_t *key_data,
+                                                  size_t key_len) {
+    return 0;
+}
+
+int Signal::Store::IdentityKeyStore::isTrustedIdentity(const signal_protocol_address *address, uint8_t *key_data,
+                                                       size_t key_len) {
+    return 0;
+}
diff --git a/qomemo/signal/stores/identity_key_store.h b/qomemo/signal/stores/identity_key_store.h
new file mode 100644
index 0000000..4179430
--- /dev/null
+++ b/qomemo/signal/stores/identity_key_store.h
@@ -0,0 +1,21 @@
+/*
+ * Created by victoria on 2021-05-13.
+ */
+
+#pragma once
+
+#include <signal/signal_protocol.h>
+
+namespace Signal::Store {
+
+    class IdentityKeyStore {
+    public:
+        static void boundToContext(signal_protocol_store_context *ctx);
+
+        int getIdentityKeyPair(signal_buffer **public_data, signal_buffer **private_data);
+        int getLocalRegistrationId(uint32_t *registration_id);
+        int saveIdentity(const signal_protocol_address *address, uint8_t *key_data, size_t key_len);
+        int isTrustedIdentity(const signal_protocol_address *address, uint8_t *key_data, size_t key_len);
+    };
+
+} // namespace Signal::Store
diff --git a/qomemo/signal/stores/pre_key_store.cpp b/qomemo/signal/stores/pre_key_store.cpp
new file mode 100644
index 0000000..fe4cd60
--- /dev/null
+++ b/qomemo/signal/stores/pre_key_store.cpp
@@ -0,0 +1,43 @@
+/*
+ * Created by victoria on 2021-05-13.
+ */
+
+#include "pre_key_store.h"
+
+void Signal::Store::PreKeyStore::boundToContext(
+        signal_protocol_store_context *ctx) {
+    signal_protocol_pre_key_store store{};
+
+    store.destroy_func = nullptr;
+    store.user_data = nullptr;
+
+    store.contains_pre_key = [](uint32_t id, void *ptr) {
+        return static_cast<PreKeyStore *>(ptr)->containsPreKey(id);
+    };
+    store.load_pre_key = [](signal_buffer **record, uint32_t id, void *ptr) {
+        return static_cast<PreKeyStore *>(ptr)->loadPreKey(record, id);
+    };
+    store.remove_pre_key = [](uint32_t id, void *ptr) {
+        return static_cast<PreKeyStore *>(ptr)->removePreKey(id);
+    };
+    store.store_pre_key = [](uint32_t id, uint8_t *record, size_t size,
+                             void *ptr) {
+        return static_cast<PreKeyStore *>(ptr)->storePreKey(id, record, size);
+    };
+
+    signal_protocol_store_context_set_pre_key_store(ctx, &store);
+}
+
+int Signal::Store::PreKeyStore::containsPreKey(uint32_t pre_key_id) {
+    return 0;
+}
+
+int Signal::Store::PreKeyStore::loadPreKey(signal_buffer **record, uint32_t pre_key_id) {
+    return 0;
+}
+
+int Signal::Store::PreKeyStore::storePreKey(uint32_t pre_key_id, uint8_t *record, size_t record_len) {
+    return 0;
+}
+
+int Signal::Store::PreKeyStore::removePreKey(uint32_t pre_key_id) { return 0; }
diff --git a/qomemo/signal/stores/pre_key_store.h b/qomemo/signal/stores/pre_key_store.h
new file mode 100644
index 0000000..eb70786
--- /dev/null
+++ b/qomemo/signal/stores/pre_key_store.h
@@ -0,0 +1,21 @@
+/*
+ * Created by victoria on 2021-05-13.
+ */
+
+#pragma once
+
+#include <signal/signal_protocol.h>
+
+namespace Signal::Store {
+
+    class PreKeyStore {
+    public:
+        static void boundToContext(signal_protocol_store_context *ctx);
+
+        int containsPreKey(uint32_t pre_key_id);
+        int loadPreKey(signal_buffer **record, uint32_t pre_key_id);
+        int storePreKey(uint32_t pre_key_id, uint8_t *record, size_t record_len);
+        int removePreKey(uint32_t pre_key_id);
+    };
+
+} // namespace Signal::Store
diff --git a/qomemo/signal/stores/sender_key_store.cpp b/qomemo/signal/stores/sender_key_store.cpp
new file mode 100644
index 0000000..a1cb687
--- /dev/null
+++ b/qomemo/signal/stores/sender_key_store.cpp
@@ -0,0 +1,36 @@
+/*
+ * Created by victoria on 2021-05-13.
+ */
+
+#include "sender_key_store.h"
+
+void Signal::Store::SenderKeyStore::boundToContext(
+        signal_protocol_store_context *ctx) {
+    signal_protocol_sender_key_store store{};
+
+    store.user_data = nullptr;
+    store.destroy_func = nullptr;
+
+    store.load_sender_key = [](signal_buffer **record, signal_buffer **user_record,
+                               const signal_protocol_sender_key_name *sender_key_name, void *ptr) {
+        return static_cast<SenderKeyStore *>(ptr)->loadSenderKey(record, user_record, sender_key_name);
+    };
+    store.store_sender_key = [](const signal_protocol_sender_key_name *sender_key_name, uint8_t *record,
+                                size_t record_len, uint8_t *user_record, size_t user_record_len, void *ptr) {
+        return static_cast<SenderKeyStore *>(ptr)->storeSenderKey(sender_key_name, record, record_len, user_record,
+                                                                  user_record_len);
+    };
+
+    signal_protocol_store_context_set_sender_key_store(ctx, &store);
+}
+
+int Signal::Store::SenderKeyStore::loadSenderKey(signal_buffer **record, signal_buffer **user_record,
+                                                 const signal_protocol_sender_key_name *sender_key_name) {
+    return 0;
+}
+
+int Signal::Store::SenderKeyStore::storeSenderKey(const signal_protocol_sender_key_name *sender_key_name,
+                                                  uint8_t *record, size_t record_len, uint8_t *user_record,
+                                                  size_t user_record_len) {
+    return 0;
+}
diff --git a/qomemo/signal/stores/sender_key_store.h b/qomemo/signal/stores/sender_key_store.h
new file mode 100644
index 0000000..904377e
--- /dev/null
+++ b/qomemo/signal/stores/sender_key_store.h
@@ -0,0 +1,21 @@
+/*
+ * Created by victoria on 2021-05-13.
+ */
+
+#pragma once
+
+#include <signal/signal_protocol.h>
+
+namespace Signal::Store {
+
+    class SenderKeyStore {
+    public:
+        static void boundToContext(signal_protocol_store_context *ctx);
+
+        int storeSenderKey(const signal_protocol_sender_key_name *sender_key_name, uint8_t *record, size_t record_len,
+                           uint8_t *user_record, size_t user_record_len);
+        int loadSenderKey(signal_buffer **record, signal_buffer **user_record,
+                          const signal_protocol_sender_key_name *sender_key_name);
+    };
+
+} // namespace Signal::Store
diff --git a/qomemo/signal/stores/session_store.cpp b/qomemo/signal/stores/session_store.cpp
new file mode 100644
index 0000000..311b3e6
--- /dev/null
+++ b/qomemo/signal/stores/session_store.cpp
@@ -0,0 +1,63 @@
+/*
+ * Created by victoria on 2021-05-13.
+ */
+
+#include "session_store.h"
+
+void Signal::Store::SessionStore::boundToContext(
+        signal_protocol_store_context *ctx) {
+    signal_protocol_session_store store{};
+
+    store.user_data = nullptr;
+    store.destroy_func = nullptr;
+
+    store.load_session_func = [](signal_buffer **record, signal_buffer **user_record,
+                                 const signal_protocol_address *address, void *ptr) {
+        return static_cast<SessionStore *>(ptr)->loadSession(record, user_record, address);
+    };
+    store.get_sub_device_sessions_func = [](signal_int_list **sessions, const char *name, size_t name_len, void *ptr) {
+        return static_cast<SessionStore *>(ptr)->getSubDeviceSessions(sessions, name, name_len);
+    };
+    store.store_session_func = [](const signal_protocol_address *address, uint8_t *record, size_t record_len,
+                                  uint8_t *user_record, size_t user_record_len, void *ptr) {
+        return static_cast<SessionStore *>(ptr)->storeSession(address, record, record_len, user_record,
+                                                              user_record_len);
+    };
+    store.contains_session_func = [](const signal_protocol_address *address, void *ptr) {
+        return static_cast<SessionStore *>(ptr)->containsSession(address);
+    };
+    store.delete_session_func = [](const signal_protocol_address *address, void *ptr) {
+        return static_cast<SessionStore *>(ptr)->deleteSession(address);
+    };
+    store.delete_all_sessions_func = [](const char *name, size_t name_len, void *ptr) {
+        return static_cast<SessionStore *>(ptr)->deleteAllSessions(name, name_len);
+    };
+
+    signal_protocol_store_context_set_session_store(ctx, &store);
+}
+
+int Signal::Store::SessionStore::loadSession(signal_buffer **record, signal_buffer **user_record,
+                                             const signal_protocol_address *address) {
+    return 0;
+}
+
+int Signal::Store::SessionStore::getSubDeviceSessions(signal_int_list **sessions, const char *name, size_t name_len) {
+    return 0;
+}
+
+int Signal::Store::SessionStore::storeSession(const signal_protocol_address *address, uint8_t *record,
+                                              size_t record_len, uint8_t *user_record, size_t user_record_len) {
+    return 0;
+}
+
+int Signal::Store::SessionStore::containsSession(const signal_protocol_address *address) {
+    return 0;
+}
+
+int Signal::Store::SessionStore::deleteSession(const signal_protocol_address *address) {
+    return 0;
+}
+
+int Signal::Store::SessionStore::deleteAllSessions(const char *name, size_t name_len) {
+    return 0;
+}
diff --git a/qomemo/signal/stores/session_store.h b/qomemo/signal/stores/session_store.h
new file mode 100644
index 0000000..848e5b8
--- /dev/null
+++ b/qomemo/signal/stores/session_store.h
@@ -0,0 +1,24 @@
+/*
+ * Created by victoria on 2021-05-13.
+ */
+
+#pragma once
+
+#include <signal/signal_protocol.h>
+
+namespace Signal::Store {
+
+    class SessionStore {
+    public:
+        static void boundToContext(signal_protocol_store_context *ctx);
+
+        int loadSession(signal_buffer **record, signal_buffer **user_record, const signal_protocol_address *address);
+        int getSubDeviceSessions(signal_int_list **sessions, const char *name, size_t name_len);
+        int storeSession(const signal_protocol_address *address, uint8_t *record, size_t record_len,
+                         uint8_t *user_record, size_t user_record_len);
+        int containsSession(const signal_protocol_address *address);
+        int deleteSession(const signal_protocol_address *address);
+        int deleteAllSessions(const char *name, size_t name_len);
+    };
+
+} // namespace Signal::Store
diff --git a/qomemo/signal/stores/signed_pre_key_store.cpp b/qomemo/signal/stores/signed_pre_key_store.cpp
new file mode 100644
index 0000000..0156894
--- /dev/null
+++ b/qomemo/signal/stores/signed_pre_key_store.cpp
@@ -0,0 +1,48 @@
+/*
+ * Created by victoria on 2021-05-13.
+ */
+
+#include "signed_pre_key_store.h"
+
+void Signal::Store::SignedPreKeyStore::boundToContext(
+        signal_protocol_store_context *ctx) {
+    signal_protocol_signed_pre_key_store store{};
+
+    store.user_data = nullptr;
+    store.destroy_func = nullptr;
+
+    store.load_signed_pre_key = [](signal_buffer **record, uint32_t signed_pre_key_id, void *ptr) {
+        return static_cast<SignedPreKeyStore *>(ptr)->loadSignedPreKey(
+                record, signed_pre_key_id);
+    };
+    store.store_signed_pre_key = [](uint32_t signed_pre_key_id, uint8_t *record, size_t record_len, void *ptr) {
+        return static_cast<SignedPreKeyStore *>(ptr)->storeSignedPreKey(
+                signed_pre_key_id, record, record_len);
+    };
+    store.contains_signed_pre_key = [](uint32_t signed_pre_key_id, void *ptr) {
+        return static_cast<SignedPreKeyStore *>(ptr)->containsSignedPreKey(
+                signed_pre_key_id);
+    };
+    store.remove_signed_pre_key = [](uint32_t signed_pre_key_id, void *ptr) {
+        return static_cast<SignedPreKeyStore *>(ptr)->removeSignedPreKey(
+                signed_pre_key_id);
+    };
+
+    signal_protocol_store_context_set_signed_pre_key_store(ctx, &store);
+}
+
+int Signal::Store::SignedPreKeyStore::loadSignedPreKey(signal_buffer **record, uint32_t signed_pre_key_id) {
+    return 0;
+}
+
+int Signal::Store::SignedPreKeyStore::storeSignedPreKey(uint32_t signed_pre_key_id, uint8_t *record, size_t record_len) {
+    return 0;
+}
+
+int Signal::Store::SignedPreKeyStore::containsSignedPreKey(uint32_t signed_pre_key_id) {
+    return 0;
+}
+
+int Signal::Store::SignedPreKeyStore::removeSignedPreKey(uint32_t signed_pre_key_id) {
+    return 0;
+}
diff --git a/qomemo/signal/stores/signed_pre_key_store.h b/qomemo/signal/stores/signed_pre_key_store.h
new file mode 100644
index 0000000..3f58851
--- /dev/null
+++ b/qomemo/signal/stores/signed_pre_key_store.h
@@ -0,0 +1,20 @@
+/*
+ * Created by victoria on 2021-05-13.
+ */
+
+#pragma once
+
+#include <signal/signal_protocol.h>
+
+namespace Signal::Store {
+
+    class SignedPreKeyStore {
+    public:
+        static void boundToContext(signal_protocol_store_context *ctx);
+        int loadSignedPreKey(signal_buffer **record, uint32_t signed_pre_key_id);
+        int storeSignedPreKey(uint32_t signed_pre_key_id, uint8_t *record, size_t record_len);
+        int containsSignedPreKey(uint32_t signed_pre_key_id);
+        int removeSignedPreKey(uint32_t signed_pre_key_id);
+    };
+
+} // namespace Signal::Store
diff --git a/qomemo/signal/stores/store_context.cpp b/qomemo/signal/stores/store_context.cpp
new file mode 100644
index 0000000..dd5bb5e
--- /dev/null
+++ b/qomemo/signal/stores/store_context.cpp
@@ -0,0 +1,13 @@
+/*
+ * Created by victoria on 2021-05-13.
+ */
+
+#include "store_context.h"
+
+Signal::Store::Context::Context(signal_context *global) {
+    signal_protocol_store_context_create(&ctx, global);
+}
+
+Signal::Store::Context::~Context() {
+    signal_protocol_store_context_destroy(ctx);
+}
diff --git a/qomemo/signal/stores/store_context.h b/qomemo/signal/stores/store_context.h
new file mode 100644
index 0000000..204d9b9
--- /dev/null
+++ b/qomemo/signal/stores/store_context.h
@@ -0,0 +1,24 @@
+/*
+ * Created by victoria on 2021-05-13.
+ */
+
+#pragma once
+
+#include <signal/signal_protocol.h>
+
+namespace Signal::Store {
+
+    class Context {
+    public:
+        explicit Context(signal_context *global);
+        ~Context();
+
+        Context(const Context &) = delete;
+        Context(Context &&) = delete;
+        Context &operator=(const Context &) = delete;
+
+    private:
+        signal_protocol_store_context *ctx{nullptr};
+    };
+
+} // namespace Signal::Store

From 574210f5d99948cb13c9124b3fd992e8a1020335 Mon Sep 17 00:00:00 2001
From: vae <vae@programming.socks.town>
Date: Thu, 13 May 2021 17:50:59 +0300
Subject: [PATCH 15/20] ref(omemo): bundle separate, add db

---
 qomemo/bundle.cpp              |   7 +-
 qomemo/bundle.h                |   2 +-
 qomemo/database.cpp            | 126 +++++++++++++++++++++++++++++++++
 qomemo/database.h              |  40 +++++++++++
 qomemo/device_service.h        |   2 +-
 qomemo/qxmpp_omemo_manager.cpp |  13 ++--
 qomemo/sce.h                   |   2 +-
 qomemo/user_device_list.h      |   1 +
 qomemo/variant/omemo_base.h    |  10 +--
 9 files changed, 189 insertions(+), 14 deletions(-)
 create mode 100644 qomemo/database.cpp
 create mode 100644 qomemo/database.h

diff --git a/qomemo/bundle.cpp b/qomemo/bundle.cpp
index aec605a..91f630d 100644
--- a/qomemo/bundle.cpp
+++ b/qomemo/bundle.cpp
@@ -64,7 +64,9 @@ QXmppIq QXmpp::Omemo::Bundle::toIq(int deviceId) const {
   item.appendChild(toXml());
 
   auto publish = createElement("publish");
-  publish.setAttribute("node", QString("eu.siacs.conversations.axolotl.bundles:%s").arg(deviceId));
+  publish.setAttribute(
+      "node",
+      QString("eu.siacs.conversations.axolotl.bundles:%s").arg(deviceId));
   publish.appendChild(item);
 
   auto pubSub = createElement("pubsub", "http://jabber.org/protocol/pubsub");
@@ -116,7 +118,8 @@ void QXmpp::Omemo::Bundle::fromXml(const QXmppElement &element) {
 QXmppPubSubIq QXmpp::Omemo::Bundle::fetchDeviceBundleIq(int deviceId) {
   QXmppPubSubIq iq{};
   iq.setType(QXmppIq::Get);
-  iq.setQueryNode(QString("eu.siacs.conversations.axolotl.bundles:%1").arg(deviceId));
+  iq.setQueryNode(
+      QString("eu.siacs.conversations.axolotl.bundles:%1").arg(deviceId));
 
   QXmppPubSubItem item{};
   item.setId(QString::number(deviceId));
diff --git a/qomemo/bundle.h b/qomemo/bundle.h
index d29bf02..bfa9fff 100644
--- a/qomemo/bundle.h
+++ b/qomemo/bundle.h
@@ -38,4 +38,4 @@ public:
   QList<PreKey> prekeys;
 };
 
-}
+} // namespace QXmpp::Omemo
diff --git a/qomemo/database.cpp b/qomemo/database.cpp
new file mode 100644
index 0000000..3644522
--- /dev/null
+++ b/qomemo/database.cpp
@@ -0,0 +1,126 @@
+/*
+ * Created by victoria on 2021-05-13.
+ */
+
+#include "database.h"
+
+#include <QDebug>
+#include <QDir>
+#include <QException>
+#include <QStandardPaths>
+#include <QString>
+
+using namespace QXmpp::Omemo;
+
+Database::Database(QString jid) : jid(std::move(jid)) {
+  auto cacheLocation =
+      QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
+  auto path = QString("%1/.omemo/%2").arg(cacheLocation, jid);
+  QDir cache(path);
+
+  if (!cache.exists() && !cache.mkpath(path)) {
+    qWarning() << "Could not create:" << path;
+    throw QException();
+  }
+
+  mdb_env_create(&env);
+
+  mdb_env_set_maxdbs(env, 5);
+  mdb_env_set_mapsize(env, 512UL * 1024UL * 1024UL);
+  mdb_env_open(env, path.toStdString().c_str(), 0, 0664);
+
+  MDB_txn *txn;
+  mdb_txn_begin(env, nullptr, 0, &txn);
+  mdb_dbi_open(txn, "keys", MDB_CREATE, &dbiKeys);
+  mdb_dbi_open(txn, "devices", MDB_CREATE, &dbiDevices);
+  mdb_dbi_open(txn, "identity_keys", MDB_CREATE, &dbiIdentityKeys);
+  mdb_txn_commit(txn);
+}
+
+Database::~Database() {
+  mdb_dbi_close(env, dbiKeys);
+  mdb_dbi_close(env, dbiDevices);
+  mdb_dbi_close(env, dbiIdentityKeys);
+  mdb_env_close(env);
+}
+
+QBuffer Database::loadIdentityKeySecret(int deviceId) { return QBuffer(); }
+
+bool Database::saveIdentityKeySecret(int deviceId,
+                                     const QBuffer &identityKeySecret) {
+  MDB_val mdbKey, mdbValue;
+  auto key = QString("%1/secret").arg(QString::number(deviceId)).toStdString();
+
+  mdbKey.mv_data = key.data();
+  mdbKey.mv_size = key.size();
+
+  mdbValue.mv_data = const_cast<char *>(identityKeySecret.data().data());
+  mdbValue.mv_size = identityKeySecret.size();
+
+  MDB_txn *txn;
+  mdb_txn_begin(env, nullptr, 0, &txn);
+  auto err = mdb_put(txn, dbiIdentityKeys, &mdbKey, &mdbValue, MDB_NOOVERWRITE);
+  if (!err) {
+    mdb_txn_commit(txn);
+    return true;
+  }
+
+  qWarning() << "could not save identity key secret:" << mdb_strerror(err);
+  mdb_txn_abort(txn);
+
+  return false;
+}
+
+int Database::loadActiveDeviceId() {
+  MDB_val key, value;
+
+  key.mv_data = (void *)"active";
+  key.mv_size = sizeof("active");
+
+  MDB_txn *txn;
+  mdb_txn_begin(env, nullptr, 0, &txn);
+
+  auto err = mdb_get(txn, dbiIdentityKeys, &key, &value);
+  if (err) {
+    qWarning() << "could not load active device id:" << mdb_strerror(err);
+    return 0;
+  }
+
+  if (value.mv_size != sizeof(int)) {
+    qWarning() << "mv_size is" << value.mv_size << "instead of" << sizeof(int);
+    return 0;
+  }
+
+  auto id = *reinterpret_cast<int *>(value.mv_data);
+
+  mdb_txn_abort(txn);
+
+  return id;
+}
+
+bool Database::saveActiveDeviceId(int deviceId) {
+  MDB_val key, value;
+
+  key.mv_data = (void *)"active";
+  key.mv_size = sizeof("active");
+
+  value.mv_data = &deviceId;
+  value.mv_size = sizeof(deviceId);
+
+  MDB_txn *txn;
+  mdb_txn_begin(env, nullptr, 0, &txn);
+
+  auto err = mdb_put(txn, dbiIdentityKeys, &key, &value, 0);
+  if (err) {
+    qWarning() << "could not save active device id" << mdb_strerror(err);
+    return false;
+  }
+
+  err = mdb_txn_commit(txn);
+  if (err) {
+    qWarning() << "could not save active device id" << mdb_strerror(err);
+    return false;
+  }
+
+  return true;
+}
diff --git a/qomemo/database.h b/qomemo/database.h
new file mode 100644
index 0000000..4584b2e
--- /dev/null
+++ b/qomemo/database.h
@@ -0,0 +1,40 @@
+/*
+ * Created by victoria on 2021-05-13.
+ */
+
+#pragma once
+
+#include <QBuffer>
+#include <QString>
+#include <lmdb.h>
+
+namespace QXmpp::Omemo {
+
+class Database {
+public:
+  explicit Database(QString jid);
+  ~Database();
+  Database(const Database &) = delete;
+  Database(Database &&) = delete;
+  Database &operator=(const Database &) = delete;
+
+  QBuffer loadIdentityKey();
+  bool saveIdentityKey(const QBuffer &identityKey);
+
+  int loadActiveDeviceId();
+  bool saveActiveDeviceId(int deviceId);
+
+  QBuffer loadIdentityKeySecret(int deviceId);
+  bool saveIdentityKeySecret(int deviceId, const QBuffer &identityKeySecret);
+
+  const QString jid;
+
+private:
+  MDB_env *env{};
+  MDB_dbi dbiDevices{};
+  MDB_dbi dbiKeys{};
+  MDB_dbi dbiPreKeys{};
+  MDB_dbi dbiIdentityKeys{};
+};
+
+} // namespace QXmpp::Omemo
diff --git a/qomemo/device_service.h b/qomemo/device_service.h
index 774b7f4..c4aa04e 100644
--- a/qomemo/device_service.h
+++ b/qomemo/device_service.h
@@ -20,7 +20,7 @@ public:
   explicit DeviceService(QObject *parent);
 
 public slots:
-  void onDeviceListReceived(const QString& jid, const DeviceList& list);
+  void onDeviceListReceived(const QString &jid, const DeviceList &list);
 
 private:
   QMap<QString, UserDeviceList> device_lists{};
diff --git a/qomemo/qxmpp_omemo_manager.cpp b/qomemo/qxmpp_omemo_manager.cpp
index f29ec98..062b934 100644
--- a/qomemo/qxmpp_omemo_manager.cpp
+++ b/qomemo/qxmpp_omemo_manager.cpp
@@ -15,8 +15,11 @@
 
 using namespace QXmpp::Omemo;
 
-Manager::Manager() : deviceService(new DeviceService(this)), omemoVariant(new Variant::Conversations) {
-  connect(this, &Manager::deviceListReceived, deviceService.get(), &DeviceService::onDeviceListReceived);
+Manager::Manager()
+    : deviceService(new DeviceService(this)),
+      omemoVariant(new Variant::Conversations) {
+  connect(this, &Manager::deviceListReceived, deviceService.get(),
+          &DeviceService::onDeviceListReceived);
 }
 
 bool QXmpp::Omemo::Manager::handleStanza(const QDomElement &stanza) {
@@ -31,7 +34,8 @@ bool QXmpp::Omemo::Manager::handleStanza(const QDomElement &stanza) {
       auto pubsub = stanza.firstChildElement("pubsub");
       if (!pubsub.isNull()) {
         auto items = pubsub.firstChildElement("items");
-        if (items.attribute("node") == "eu.siacs.conversations.axolotl.devicelist") {
+        if (items.attribute("node") ==
+            "eu.siacs.conversations.axolotl.devicelist") {
           auto item = items.firstChildElement("item");
           if (!item.isNull()) {
             auto list = item.firstChildElement("list");
@@ -56,7 +60,8 @@ void QXmpp::Omemo::Manager::setClient(QXmppClient *client) {
   if (!client)
     return;
 
-  QObject::connect(client, &QXmppClient::connected, this, &Manager::fetchOwnDevices);
+  QObject::connect(client, &QXmppClient::connected, this,
+                   &Manager::fetchOwnDevices);
 }
 
 void QXmpp::Omemo::Manager::fetchOwnDevices() {
diff --git a/qomemo/sce.h b/qomemo/sce.h
index 2cea049..91a7b2c 100644
--- a/qomemo/sce.h
+++ b/qomemo/sce.h
@@ -4,8 +4,8 @@
 
 #pragma once
 
-#include <QXmppElement.h>
 #include <QDateTime>
+#include <QXmppElement.h>
 
 namespace QXmpp::Sce {
 
diff --git a/qomemo/user_device_list.h b/qomemo/user_device_list.h
index 26a88ca..27e407a 100644
--- a/qomemo/user_device_list.h
+++ b/qomemo/user_device_list.h
@@ -5,6 +5,7 @@
 #pragma once
 
 #include <QString>
+
 namespace QXmpp::Omemo {
 
 class UserDeviceList {
diff --git a/qomemo/variant/omemo_base.h b/qomemo/variant/omemo_base.h
index f383a59..fd11f31 100644
--- a/qomemo/variant/omemo_base.h
+++ b/qomemo/variant/omemo_base.h
@@ -18,12 +18,12 @@ class Base {
 public:
   virtual ~Base() = default;
 
-  virtual QXmppElement deviceToXml(const Device& device) = 0;
-  virtual Device deviceFromXml(const QXmppElement& xml) = 0;
+  virtual QXmppElement deviceToXml(const Device &device) = 0;
+  virtual Device deviceFromXml(const QXmppElement &xml) = 0;
 
-  virtual QXmppElement deviceListToXml(const DeviceList& deviceList) = 0;
-  virtual DeviceList deviceListFromXml(const QXmppElement& xml) = 0;
-  virtual QXmppIq deviceListSetIq(const DeviceList& deviceList) = 0;
+  virtual QXmppElement deviceListToXml(const DeviceList &deviceList) = 0;
+  virtual DeviceList deviceListFromXml(const QXmppElement &xml) = 0;
+  virtual QXmppIq deviceListSetIq(const DeviceList &deviceList) = 0;
 };
 
 } // namespace Variant

From bbeeee4c8a119ce90bd840459a66077b84e9f1f0 Mon Sep 17 00:00:00 2001
From: vae <vae@programming.socks.town>
Date: Thu, 13 May 2021 17:54:37 +0300
Subject: [PATCH 16/20] ref(omemo): reformat qomemo/

---
 qomemo/CMakeLists.txt                         |  38 ++---
 qomemo/TODO                                   |  12 --
 qomemo/bundle.cpp                             | 156 +++++++++---------
 qomemo/bundle.h                               |  42 ++---
 qomemo/database.cpp                           | 146 ++++++++--------
 qomemo/database.h                             |  42 ++---
 qomemo/device.h                               |  17 +-
 qomemo/device_key_storage.cpp                 |   7 +-
 qomemo/device_key_storage.h                   |  12 +-
 qomemo/device_service.cpp                     |   9 +-
 qomemo/device_service.h                       |  20 +--
 qomemo/qomemo.cpp                             |  98 +++++------
 qomemo/qomemo.h                               |  42 ++---
 qomemo/qxmpp_omemo_manager.cpp                |  73 ++++----
 qomemo/qxmpp_omemo_manager.h                  |  32 ++--
 qomemo/sce.cpp                                |  20 ++-
 qomemo/sce.h                                  |   2 +-
 qomemo/signal/stores/signed_pre_key_store.cpp |   3 +-
 qomemo/user_device_list.cpp                   |   3 +-
 qomemo/user_device_list.h                     |  10 +-
 qomemo/variant/conversations.cpp              |  72 ++++----
 qomemo/variant/conversations.h                |  18 +-
 qomemo/variant/omemo_base.h                   |  28 ++--
 23 files changed, 447 insertions(+), 455 deletions(-)
 delete mode 100644 qomemo/TODO

diff --git a/qomemo/CMakeLists.txt b/qomemo/CMakeLists.txt
index 7391404..8b194be 100644
--- a/qomemo/CMakeLists.txt
+++ b/qomemo/CMakeLists.txt
@@ -1,23 +1,23 @@
 target_sources(squawk PRIVATE
-  bundle.cpp
-  bundle.h
-  database.cpp
-  database.h
-  device.cpp
-  device.h
-  device_key_storage.cpp
-  device_key_storage.h
-  device_service.cpp
-  device_service.h
-  qomemo.cpp
-  qomemo.h
-  sce.cpp
-  sce.h
-  user_device_list.cpp
-  user_device_list.h
-  qxmpp_omemo_manager.cpp
-  qxmpp_omemo_manager.h
-  )
+        bundle.cpp
+        bundle.h
+        database.cpp
+        database.h
+        device.cpp
+        device.h
+        device_key_storage.cpp
+        device_key_storage.h
+        device_service.cpp
+        device_service.h
+        qomemo.cpp
+        qomemo.h
+        sce.cpp
+        sce.h
+        user_device_list.cpp
+        user_device_list.h
+        qxmpp_omemo_manager.cpp
+        qxmpp_omemo_manager.h
+        )
 
 add_subdirectory(signal)
 add_subdirectory(variant)
diff --git a/qomemo/TODO b/qomemo/TODO
deleted file mode 100644
index 6a5709d..0000000
--- a/qomemo/TODO
+++ /dev/null
@@ -1,12 +0,0 @@
-* Generate device w/ keys
-* PubSub set urn:xmpp:omemo:1:devices to announce new device
-* PubSub set urn:xmpp:omemo:1:bundles to announce new key bundles
-* PubSub get urn:xmpp:omemo:1:bundles to get user bundles
-
-Sending a message:
-* Create urn:xmpp:sce:0 with padding and content
-* Add <encrypted xmlns='urn:xmpp:omemo:1'> with header (key list) and payload (b64)
-
-Receiving a message:
-* Check <keys> => <key>
-* Decrypt (TODO)
\ No newline at end of file
diff --git a/qomemo/bundle.cpp b/qomemo/bundle.cpp
index 91f630d..691a65e 100644
--- a/qomemo/bundle.cpp
+++ b/qomemo/bundle.cpp
@@ -13,117 +13,117 @@
 using namespace QXmpp::Factories;
 
 QXmppElement QXmpp::Omemo::PreKey::toXml() const {
-  auto pk = createElement("preKeyPublic");
-  pk.setAttribute("preKeyId", QString::number(id));
-  // TODO: Base64
-  pk.setValue(data);
+    auto pk = createElement("preKeyPublic");
+    pk.setAttribute("preKeyId", QString::number(id));
+    // TODO: Base64
+    pk.setValue(data);
 
-  return pk;
+    return pk;
 }
 
 void QXmpp::Omemo::PreKey::fromXml(const QXmppElement &element) {
-  if (!elementMatches(element, "preKeyPublic"))
-    return;
+    if (!elementMatches(element, "preKeyPublic"))
+        return;
 
-  id = element.attribute("preKeyId").toInt();
-  // TODO: Base64
-  data = element.value();
+    id = element.attribute("preKeyId").toInt();
+    // TODO: Base64
+    data = element.value();
 }
 
 QXmppElement QXmpp::Omemo::Bundle::toXml() const {
-  auto spkNode = createElement("signedPreKeyPublic");
-  spkNode.setAttribute("signedPreKeyId", QString::number(spkId));
-  spkNode.setValue(spk);
+    auto spkNode = createElement("signedPreKeyPublic");
+    spkNode.setAttribute("signedPreKeyId", QString::number(spkId));
+    spkNode.setValue(spk);
 
-  auto spksNode = createElement("signedPreKeySignature");
-  spksNode.setValue(spks);
+    auto spksNode = createElement("signedPreKeySignature");
+    spksNode.setValue(spks);
 
-  auto ikNode = createElement("identityKey");
-  ikNode.setValue(ik);
+    auto ikNode = createElement("identityKey");
+    ikNode.setValue(ik);
 
-  auto prekeysNode = createElement("prekeys");
-  for (const auto &pk : prekeys) {
-    prekeysNode.appendChild(pk.toXml());
-  }
+    auto prekeysNode = createElement("prekeys");
+    for (const auto &pk : prekeys) {
+        prekeysNode.appendChild(pk.toXml());
+    }
 
-  auto result = createElement("bundle", "eu.siacs.conversations.axolotl");
-  result.appendChild(spkNode);
-  result.appendChild(spksNode);
-  result.appendChild(ikNode);
-  result.appendChild(prekeysNode);
+    auto result = createElement("bundle", "eu.siacs.conversations.axolotl");
+    result.appendChild(spkNode);
+    result.appendChild(spksNode);
+    result.appendChild(ikNode);
+    result.appendChild(prekeysNode);
 
-  return result;
+    return result;
 }
 
 QXmppIq QXmpp::Omemo::Bundle::toIq(int deviceId) const {
-  QXmppIq iq{};
+    QXmppIq iq{};
 
-  iq.setType(QXmppIq::Set);
+    iq.setType(QXmppIq::Set);
 
-  auto item = createElement("item");
-  item.appendChild(toXml());
+    auto item = createElement("item");
+    item.appendChild(toXml());
 
-  auto publish = createElement("publish");
-  publish.setAttribute(
-      "node",
-      QString("eu.siacs.conversations.axolotl.bundles:%s").arg(deviceId));
-  publish.appendChild(item);
+    auto publish = createElement("publish");
+    publish.setAttribute(
+            "node",
+            QString("eu.siacs.conversations.axolotl.bundles:%s").arg(deviceId));
+    publish.appendChild(item);
 
-  auto pubSub = createElement("pubsub", "http://jabber.org/protocol/pubsub");
-  pubSub.appendChild(publish);
-  pubSub.appendChild(createOpenPublishOptions());
+    auto pubSub = createElement("pubsub", "http://jabber.org/protocol/pubsub");
+    pubSub.appendChild(publish);
+    pubSub.appendChild(createOpenPublishOptions());
 
-  iq.extensions().push_back(pubSub);
+    iq.extensions().push_back(pubSub);
 
-  return iq;
+    return iq;
 }
 
 void QXmpp::Omemo::Bundle::fromXml(const QXmppElement &element) {
-  if (!elementMatches(element, "bundle", "eu.siacs.conversations.axolotl"))
-    return;
+    if (!elementMatches(element, "bundle", "eu.siacs.conversations.axolotl"))
+        return;
 
-  auto spkNode = element.firstChildElement("spk");
-  if (spkNode.isNull()) {
-    qWarning() << "'bundle': missing 'spk'";
-    return;
-  }
-  spk = spkNode.value();
-  spkId = spkNode.attribute("id").toInt();
+    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 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 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");
+    auto prekeysNode = element.firstChildElement("prekeys");
+    auto pkNode = prekeysNode.firstChildElement("pk");
 
-  prekeys.clear();
-  while (!pkNode.isNull()) {
-    PreKey pk{};
-    pk.fromXml(pkNode);
-    prekeys.push_back(pk);
-  }
+    prekeys.clear();
+    while (!pkNode.isNull()) {
+        PreKey pk{};
+        pk.fromXml(pkNode);
+        prekeys.push_back(pk);
+    }
 }
 
 QXmppPubSubIq QXmpp::Omemo::Bundle::fetchDeviceBundleIq(int deviceId) {
-  QXmppPubSubIq iq{};
-  iq.setType(QXmppIq::Get);
-  iq.setQueryNode(
-      QString("eu.siacs.conversations.axolotl.bundles:%1").arg(deviceId));
+    QXmppPubSubIq iq{};
+    iq.setType(QXmppIq::Get);
+    iq.setQueryNode(
+            QString("eu.siacs.conversations.axolotl.bundles:%1").arg(deviceId));
 
-  QXmppPubSubItem item{};
-  item.setId(QString::number(deviceId));
-  iq.setItems({item});
+    QXmppPubSubItem item{};
+    item.setId(QString::number(deviceId));
+    iq.setItems({item});
 
-  return iq;
+    return iq;
 }
\ No newline at end of file
diff --git a/qomemo/bundle.h b/qomemo/bundle.h
index bfa9fff..428277b 100644
--- a/qomemo/bundle.h
+++ b/qomemo/bundle.h
@@ -8,34 +8,36 @@
 #include <QString>
 
 class QXmppPubSubIq;
+
 class QXmppElement;
+
 class QXmppIq;
 
 namespace QXmpp::Omemo {
 
-class PreKey {
-public:
-  [[nodiscard]] QXmppElement toXml() const;
-  /// Expects a <pk>
-  void fromXml(const QXmppElement &element);
+    class PreKey {
+    public:
+        [[nodiscard]] QXmppElement toXml() const;
+        /// Expects a <pk>
+        void fromXml(const QXmppElement &element);
 
-  int id;
-  QString data;
-};
+        int id;
+        QString data;
+    };
 
-class Bundle {
-public:
-  [[nodiscard]] static QXmppPubSubIq fetchDeviceBundleIq(int deviceId);
+    class Bundle {
+    public:
+        [[nodiscard]] static QXmppPubSubIq fetchDeviceBundleIq(int deviceId);
 
-  [[nodiscard]] QXmppElement toXml() const;
-  [[nodiscard]] QXmppIq toIq(int deviceId) const;
-  void fromXml(const QXmppElement &element);
+        [[nodiscard]] QXmppElement toXml() const;
+        [[nodiscard]] QXmppIq toIq(int deviceId) const;
+        void fromXml(const QXmppElement &element);
 
-  QString spk;
-  int spkId;
-  QString spks;
-  QString ik;
-  QList<PreKey> prekeys;
-};
+        QString spk;
+        int spkId;
+        QString spks;
+        QString ik;
+        QList<PreKey> prekeys;
+    };
 
 } // namespace QXmpp::Omemo
diff --git a/qomemo/database.cpp b/qomemo/database.cpp
index 3644522..2314a80 100644
--- a/qomemo/database.cpp
+++ b/qomemo/database.cpp
@@ -13,114 +13,114 @@
 using namespace QXmpp::Omemo;
 
 Database::Database(QString jid) : jid(std::move(jid)) {
-  auto cacheLocation =
-      QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
-  auto path = QString("%1/.omemo/%2").arg(cacheLocation, jid);
-  QDir cache(path);
+    auto cacheLocation =
+            QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
+    auto path = QString("%1/.omemo/%2").arg(cacheLocation, jid);
+    QDir cache(path);
 
-  if (!cache.exists() && !cache.mkpath(path)) {
-    qWarning() << "Could not create:" << path;
-    throw QException();
-  }
+    if (!cache.exists() && !cache.mkpath(path)) {
+        qWarning() << "Could not create:" << path;
+        throw QException();
+    }
 
-  mdb_env_create(&env);
+    mdb_env_create(&env);
 
-  mdb_env_set_maxdbs(env, 5);
-  mdb_env_set_mapsize(env, 512UL * 1024UL * 1024UL);
-  mdb_env_open(env, path.toStdString().c_str(), 0, 0664);
+    mdb_env_set_maxdbs(env, 5);
+    mdb_env_set_mapsize(env, 512UL * 1024UL * 1024UL);
+    mdb_env_open(env, path.toStdString().c_str(), 0, 0664);
 
-  MDB_txn *txn;
-  mdb_txn_begin(env, nullptr, 0, &txn);
-  mdb_dbi_open(txn, "keys", MDB_CREATE, &dbiKeys);
-  mdb_dbi_open(txn, "devices", MDB_CREATE, &dbiDevices);
-  mdb_dbi_open(txn, "identity_keys", MDB_CREATE, &dbiIdentityKeys);
-  mdb_txn_commit(txn);
+    MDB_txn *txn;
+    mdb_txn_begin(env, nullptr, 0, &txn);
+    mdb_dbi_open(txn, "keys", MDB_CREATE, &dbiKeys);
+    mdb_dbi_open(txn, "devices", MDB_CREATE, &dbiDevices);
+    mdb_dbi_open(txn, "identity_keys", MDB_CREATE, &dbiIdentityKeys);
+    mdb_txn_commit(txn);
 }
 
 Database::~Database() {
-  mdb_dbi_close(env, dbiKeys);
-  mdb_dbi_close(env, dbiDevices);
-  mdb_dbi_close(env, dbiIdentityKeys);
-  mdb_env_close(env);
+    mdb_dbi_close(env, dbiKeys);
+    mdb_dbi_close(env, dbiDevices);
+    mdb_dbi_close(env, dbiIdentityKeys);
+    mdb_env_close(env);
 }
 
 QBuffer Database::loadIdentityKeySecret(int deviceId) { return QBuffer(); }
 
 bool Database::saveIdentityKeySecret(int deviceId,
                                      const QBuffer &identityKeySecret) {
-  MDB_val mdbKey, mdbValue;
-  auto key = QString("%1/secret").arg(QString::number(deviceId)).toStdString();
+    MDB_val mdbKey, mdbValue;
+    auto key = QString("%1/secret").arg(QString::number(deviceId)).toStdString();
 
-  mdbKey.mv_data = key.data();
-  mdbKey.mv_size = key.size();
+    mdbKey.mv_data = key.data();
+    mdbKey.mv_size = key.size();
 
-  mdbValue.mv_data = const_cast<char *>(identityKeySecret.data().data());
-  mdbValue.mv_size = identityKeySecret.size();
+    mdbValue.mv_data = const_cast<char *>(identityKeySecret.data().data());
+    mdbValue.mv_size = identityKeySecret.size();
 
-  MDB_txn *txn;
-  mdb_txn_begin(env, nullptr, 0, &txn);
-  auto err = mdb_put(txn, dbiIdentityKeys, &mdbKey, &mdbValue, MDB_NOOVERWRITE);
-  if (!err) {
-    mdb_txn_commit(txn);
-    return true;
-  }
+    MDB_txn *txn;
+    mdb_txn_begin(env, nullptr, 0, &txn);
+    auto err = mdb_put(txn, dbiIdentityKeys, &mdbKey, &mdbValue, MDB_NOOVERWRITE);
+    if (!err) {
+        mdb_txn_commit(txn);
+        return true;
+    }
 
-  qWarning() << "could not save identity key secret:" << mdb_strerror(err);
-  mdb_txn_abort(txn);
+    qWarning() << "could not save identity key secret:" << mdb_strerror(err);
+    mdb_txn_abort(txn);
 
-  return false;
+    return false;
 }
 
 int Database::loadActiveDeviceId() {
-  MDB_val key, value;
+    MDB_val key, value;
 
-  key.mv_data = (void *)"active";
-  key.mv_size = sizeof("active");
+    key.mv_data = (void *) "active";
+    key.mv_size = sizeof("active");
 
-  MDB_txn *txn;
-  mdb_txn_begin(env, nullptr, 0, &txn);
+    MDB_txn *txn;
+    mdb_txn_begin(env, nullptr, 0, &txn);
 
-  auto err = mdb_get(txn, dbiIdentityKeys, &key, &value);
-  if (err) {
-    qWarning() << "could not load active device id:" << mdb_strerror(err);
-    return 0;
-  }
+    auto err = mdb_get(txn, dbiIdentityKeys, &key, &value);
+    if (err) {
+        qWarning() << "could not load active device id:" << mdb_strerror(err);
+        return 0;
+    }
 
-  if (value.mv_size != sizeof(int)) {
-    qWarning() << "mv_size is" << value.mv_size << "instead of" << sizeof(int);
-    return 0;
-  }
+    if (value.mv_size != sizeof(int)) {
+        qWarning() << "mv_size is" << value.mv_size << "instead of" << sizeof(int);
+        return 0;
+    }
 
-  auto id = *reinterpret_cast<int *>(value.mv_data);
+    auto id = *reinterpret_cast<int *>(value.mv_data);
 
-  mdb_txn_abort(txn);
+    mdb_txn_abort(txn);
 
-  return id;
+    return id;
 }
 
 bool Database::saveActiveDeviceId(int deviceId) {
-  MDB_val key, value;
+    MDB_val key, value;
 
-  key.mv_data = (void *)"active";
-  key.mv_size = sizeof("active");
+    key.mv_data = (void *) "active";
+    key.mv_size = sizeof("active");
 
-  value.mv_data = &deviceId;
-  value.mv_size = sizeof(deviceId);
+    value.mv_data = &deviceId;
+    value.mv_size = sizeof(deviceId);
 
-  MDB_txn *txn;
-  mdb_txn_begin(env, nullptr, 0, &txn);
+    MDB_txn *txn;
+    mdb_txn_begin(env, nullptr, 0, &txn);
 
-  auto err = mdb_put(txn, dbiIdentityKeys, &key, &value, 0);
-  if (err) {
-    qWarning() << "could not save active device id" << mdb_strerror(err);
-    return false;
-  }
+    auto err = mdb_put(txn, dbiIdentityKeys, &key, &value, 0);
+    if (err) {
+        qWarning() << "could not save active device id" << mdb_strerror(err);
+        return false;
+    }
 
-  err = mdb_txn_commit(txn);
-  if (err) {
-    qWarning() << "could not save active device id" << mdb_strerror(err);
-    return false;
-  }
+    err = mdb_txn_commit(txn);
+    if (err) {
+        qWarning() << "could not save active device id" << mdb_strerror(err);
+        return false;
+    }
 
-  return true;
+    return true;
 }
diff --git a/qomemo/database.h b/qomemo/database.h
index 4584b2e..3f9b933 100644
--- a/qomemo/database.h
+++ b/qomemo/database.h
@@ -10,31 +10,31 @@
 
 namespace QXmpp::Omemo {
 
-class Database {
-public:
-  explicit Database(QString jid);
-  ~Database();
-  Database(const Database &) = delete;
-  Database(Database &&) = delete;
-  Database &operator=(const Database &) = delete;
+    class Database {
+    public:
+        explicit Database(QString jid);
+        ~Database();
+        Database(const Database &) = delete;
+        Database(Database &&) = delete;
+        Database &operator=(const Database &) = delete;
 
-  QBuffer loadIdentityKey();
-  bool saveIdentityKey(const QBuffer &identityKey);
+        QBuffer loadIdentityKey();
+        bool saveIdentityKey(const QBuffer &identityKey);
 
-  int loadActiveDeviceId();
-  bool saveActiveDeviceId(int deviceId);
+        int loadActiveDeviceId();
+        bool saveActiveDeviceId(int deviceId);
 
-  QBuffer loadIdentityKeySecret(int deviceId);
-  bool saveIdentityKeySecret(int deviceId, const QBuffer &identityKeySecret);
+        QBuffer loadIdentityKeySecret(int deviceId);
+        bool saveIdentityKeySecret(int deviceId, const QBuffer &identityKeySecret);
 
-  const QString jid;
+        const QString jid;
 
-private:
-  MDB_env *env{};
-  MDB_dbi dbiDevices{};
-  MDB_dbi dbiKeys{};
-  MDB_dbi dbiPreKeys{};
-  MDB_dbi dbiIdentityKeys{};
-};
+    private:
+        MDB_env *env{};
+        MDB_dbi dbiDevices{};
+        MDB_dbi dbiKeys{};
+        MDB_dbi dbiPreKeys{};
+        MDB_dbi dbiIdentityKeys{};
+    };
 
 } // namespace QXmpp::Omemo
diff --git a/qomemo/device.h b/qomemo/device.h
index 4af6ada..6a67a19 100644
--- a/qomemo/device.h
+++ b/qomemo/device.h
@@ -7,18 +7,19 @@
 #include <QList>
 
 class QXmppElement;
+
 class QXmppIq;
 
 namespace QXmpp::Omemo {
 
-class Device {
-public:
-  int id;
-};
+    class Device {
+    public:
+        int id;
+    };
 
-class DeviceList {
-public:
-  QList<Device> devices;
-};
+    class DeviceList {
+    public:
+        QList<Device> devices;
+    };
 
 } // namespace QXmpp::Omemo
diff --git a/qomemo/device_key_storage.cpp b/qomemo/device_key_storage.cpp
index 1ba017e..d8c74df 100644
--- a/qomemo/device_key_storage.cpp
+++ b/qomemo/device_key_storage.cpp
@@ -6,10 +6,9 @@
 #include <QRandomGenerator>
 
 int QXmpp::Omemo::DeviceKeyStorage::generateDeviceId() {
-  QRandomGenerator random{};
+    QRandomGenerator random{};
 
-  return 1 + random.bounded(INT32_MAX - 1);
+    return 1 + random.bounded(INT32_MAX - 1);
 }
 
-QXmpp::Omemo::DeviceKeyStorage::DeviceKeyStorage(int deviceId)
-    : deviceId(deviceId) {}
+QXmpp::Omemo::DeviceKeyStorage::DeviceKeyStorage(int deviceId) : deviceId(deviceId) {}
diff --git a/qomemo/device_key_storage.h b/qomemo/device_key_storage.h
index a0d8ce2..efeade9 100644
--- a/qomemo/device_key_storage.h
+++ b/qomemo/device_key_storage.h
@@ -6,13 +6,13 @@
 
 namespace QXmpp::Omemo {
 
-class DeviceKeyStorage {
-public:
-  static int generateDeviceId();
+    class DeviceKeyStorage {
+    public:
+        static int generateDeviceId();
 
-  explicit DeviceKeyStorage(int deviceId);
+        explicit DeviceKeyStorage(int deviceId);
 
-  const int deviceId;
-};
+        const int deviceId;
+    };
 
 } // namespace QXmpp::Omemo
diff --git a/qomemo/device_service.cpp b/qomemo/device_service.cpp
index fcc6b72..306f383 100644
--- a/qomemo/device_service.cpp
+++ b/qomemo/device_service.cpp
@@ -7,10 +7,9 @@
 
 QXmpp::Omemo::DeviceService::DeviceService(QObject *parent) : QObject(parent) {}
 
-void QXmpp::Omemo::DeviceService::onDeviceListReceived(
-    const QString &jid, const QXmpp::Omemo::DeviceList &list) {
+void QXmpp::Omemo::DeviceService::onDeviceListReceived(const QString &jid, const QXmpp::Omemo::DeviceList &list) {
 
-  for (const auto &device : list.devices) {
-    qInfo() << "Got device for" << jid << ":" << device.id;
-  }
+    for (const auto &device : list.devices) {
+        qInfo() << "Got device for" << jid << ":" << device.id;
+    }
 }
diff --git a/qomemo/device_service.h b/qomemo/device_service.h
index c4aa04e..a9529c4 100644
--- a/qomemo/device_service.h
+++ b/qomemo/device_service.h
@@ -11,19 +11,19 @@
 
 namespace QXmpp::Omemo {
 
-class DeviceList;
+    class DeviceList;
 
-class DeviceService : public QObject {
-  Q_OBJECT
+    class DeviceService : public QObject {
+    Q_OBJECT
 
-public:
-  explicit DeviceService(QObject *parent);
+    public:
+        explicit DeviceService(QObject *parent);
 
-public slots:
-  void onDeviceListReceived(const QString &jid, const DeviceList &list);
+    public slots:
+        void onDeviceListReceived(const QString &jid, const QXmpp::Omemo::DeviceList &list);
 
-private:
-  QMap<QString, UserDeviceList> device_lists{};
-};
+    private:
+        QMap<QString, UserDeviceList> device_lists{};
+    };
 
 } // namespace QXmpp::Omemo
diff --git a/qomemo/qomemo.cpp b/qomemo/qomemo.cpp
index d078f37..2b67119 100644
--- a/qomemo/qomemo.cpp
+++ b/qomemo/qomemo.cpp
@@ -13,82 +13,82 @@
 using namespace QXmpp::Factories;
 
 QXmppElement QXmpp::Omemo::EncryptedMessage::header() const {
-  auto result = createElement("header");
-  result.setAttribute("sid", QString::number(fromDeviceId));
+    auto result = createElement("header");
+    result.setAttribute("sid", QString::number(fromDeviceId));
 
-  auto ivNode = createElement("iv");
-  ivNode.setValue(iv);
+    auto ivNode = createElement("iv");
+    ivNode.setValue(iv);
 
-  for (const auto &key : keys) {
-    result.appendChild(key.toXml());
-  }
+    for (const auto &key : keys) {
+        result.appendChild(key.toXml());
+    }
 
-  return result;
+    return result;
 }
 
 QXmppElement QXmpp::Omemo::EncryptedMessage::toXml() const {
-  auto result = createElement("encrypted", "eu.siacs.conversations.axolotl");
+    auto result = createElement("encrypted", "eu.siacs.conversations.axolotl");
 
-  result.appendChild(header());
-  // TODO: Payload is optional
-  result.appendChild(payload());
+    result.appendChild(header());
+    // TODO: Payload is optional
+    result.appendChild(payload());
 
-  return result;
+    return result;
 }
 
 QXmppElement QXmpp::Omemo::EncryptedMessage::payload() const {
-  QBuffer buffer;
-  buffer.open(QIODevice::ReadWrite);
-  QXmlStreamWriter writer(&buffer);
-  message.toXml(&writer);
+    QBuffer buffer;
+    buffer.open(QIODevice::ReadWrite);
+    QXmlStreamWriter writer(&buffer);
+    message.toXml(&writer);
 
-  QDomDocument doc;
-  doc.setContent(buffer.data(), true);
+    QDomDocument doc;
+    doc.setContent(buffer.data(), true);
 
-  QXmppElement root(doc.documentElement());
-  root.setTagName("payload");
+    QXmppElement root(doc.documentElement());
+    root.setTagName("payload");
 
-  return root;
+    return root;
 }
 
 QXmppElement QXmpp::Omemo::EncryptedMessage::content() const {
-  auto envelope = createElement("content", "urn:xmpp:sce:0");
+    auto envelope = createElement("content", "urn:xmpp:sce:0");
 
-  envelope.appendChild(payload());
+    envelope.appendChild(payload());
 
-  if (!from.isEmpty()) {
-    auto fromNode = createElement("from");
-    fromNode.setAttribute("jid", from);
-    envelope.appendChild(fromNode);
-  }
+    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 (!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);
-  }
+    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);
+    auto rpad = createElement("rpad");
+    rpad.setValue(QXmpp::Sce::generatePadding());
+    envelope.appendChild(rpad);
 
-  return envelope;
+    return envelope;
 }
 
 QXmppElement QXmpp::Omemo::MessageKey::toXml() const {
-  auto result = createElement("key");
+    auto result = createElement("key");
 
-  result.setAttribute("rid", QString::number(receivingDeviceId));
-  if (prekey)
-    result.setAttribute("prekey", "true");
+    result.setAttribute("rid", QString::number(receivingDeviceId));
+    if (prekey)
+        result.setAttribute("prekey", "true");
 
-  result.setValue(key);
+    result.setValue(key);
 
-  return result;
+    return result;
 }
diff --git a/qomemo/qomemo.h b/qomemo/qomemo.h
index b79ef1b..1e0c71a 100644
--- a/qomemo/qomemo.h
+++ b/qomemo/qomemo.h
@@ -11,32 +11,32 @@
 
 namespace QXmpp::Omemo {
 
-class MessageKey {
-public:
-  [[nodiscard]] QXmppElement toXml() const;
+    class MessageKey {
+    public:
+        [[nodiscard]] QXmppElement toXml() const;
 
-  int receivingDeviceId{};
-  bool prekey{};
-  QString key{};
-};
+        int receivingDeviceId{};
+        bool prekey{};
+        QString key{};
+    };
 
-class EncryptedMessage {
-public:
-  [[nodiscard]] QXmppElement header() const;
-  [[nodiscard]] QXmppElement content() const;
-  [[nodiscard]] QXmppElement toXml() const;
-  [[nodiscard]] QXmppElement payload() const;
+    class EncryptedMessage {
+    public:
+        [[nodiscard]] QXmppElement header() const;
+        [[nodiscard]] QXmppElement content() const;
+        [[nodiscard]] QXmppElement toXml() const;
+        [[nodiscard]] QXmppElement payload() const;
 
-  int fromDeviceId{};
+        int fromDeviceId{};
 
-  QList<MessageKey> keys{};
-  QString from{};
-  QString to{};
-  QDateTime timestamp{};
+        QList<MessageKey> keys{};
+        QString from{};
+        QString to{};
+        QDateTime timestamp{};
 
-  QString iv{};
+        QString iv{};
 
-  QXmppMessage message{};
-};
+        QXmppMessage message{};
+    };
 
 } // namespace QXmpp::Omemo
diff --git a/qomemo/qxmpp_omemo_manager.cpp b/qomemo/qxmpp_omemo_manager.cpp
index 062b934..844a189 100644
--- a/qomemo/qxmpp_omemo_manager.cpp
+++ b/qomemo/qxmpp_omemo_manager.cpp
@@ -16,60 +16,59 @@
 using namespace QXmpp::Omemo;
 
 Manager::Manager()
-    : deviceService(new DeviceService(this)),
-      omemoVariant(new Variant::Conversations) {
-  connect(this, &Manager::deviceListReceived, deviceService.get(),
-          &DeviceService::onDeviceListReceived);
+        : deviceService(new DeviceService(this)),
+          omemoVariant(new Variant::Conversations) {
+    connect(this, &Manager::deviceListReceived, deviceService.get(), &DeviceService::onDeviceListReceived);
 }
 
 bool QXmpp::Omemo::Manager::handleStanza(const QDomElement &stanza) {
-  QString str{};
-  QTextStream info(&str);
-  stanza.save(info, 4);
+    QString str{};
+    QTextStream info(&str);
+    stanza.save(info, 4);
 
-  std::cout << str.toStdString();
+    std::cout << str.toStdString();
 
-  if (stanza.tagName() == "iq") {
-    if (stanza.attribute("type") == "result") {
-      auto pubsub = stanza.firstChildElement("pubsub");
-      if (!pubsub.isNull()) {
-        auto items = pubsub.firstChildElement("items");
-        if (items.attribute("node") ==
-            "eu.siacs.conversations.axolotl.devicelist") {
-          auto item = items.firstChildElement("item");
-          if (!item.isNull()) {
-            auto list = item.firstChildElement("list");
-            if (!list.isNull()) {
-              DeviceList deviceList = omemoVariant->deviceListFromXml(list);
-              emit deviceListReceived(stanza.attribute("from"), deviceList);
+    if (stanza.tagName() == "iq") {
+        if (stanza.attribute("type") == "result") {
+            auto pubsub = stanza.firstChildElement("pubsub");
+            if (!pubsub.isNull()) {
+                auto items = pubsub.firstChildElement("items");
+                if (items.attribute("node") ==
+                    "eu.siacs.conversations.axolotl.devicelist") {
+                    auto item = items.firstChildElement("item");
+                    if (!item.isNull()) {
+                        auto list = item.firstChildElement("list");
+                        if (!list.isNull()) {
+                            DeviceList deviceList = omemoVariant->deviceListFromXml(list);
+                            emit deviceListReceived(stanza.attribute("from"), deviceList);
 
-              return true;
+                            return true;
+                        }
+                    }
+                }
             }
-          }
         }
-      }
     }
-  }
 
-  return false;
+    return false;
 }
 
 void QXmpp::Omemo::Manager::setClient(QXmppClient *client) {
-  QXmppClientExtension::setClient(client);
+    QXmppClientExtension::setClient(client);
 
-  if (!client)
-    return;
+    if (!client)
+        return;
 
-  QObject::connect(client, &QXmppClient::connected, this,
-                   &Manager::fetchOwnDevices);
+    QObject::connect(client, &QXmppClient::connected, this,
+                     &Manager::fetchOwnDevices);
 }
 
 void QXmpp::Omemo::Manager::fetchOwnDevices() {
-  QXmppPubSubIq iq{};
-  iq.setFrom(client()->configuration().jid());
-  iq.setTo(client()->configuration().jidBare());
-  iq.setType(QXmppIq::Get);
-  iq.setQueryNode("eu.siacs.conversations.axolotl.devicelist");
+    QXmppPubSubIq iq{};
+    iq.setFrom(client()->configuration().jid());
+    iq.setTo(client()->configuration().jidBare());
+    iq.setType(QXmppIq::Get);
+    iq.setQueryNode("eu.siacs.conversations.axolotl.devicelist");
 
-  client()->sendPacket(iq);
+    client()->sendPacket(iq);
 }
diff --git a/qomemo/qxmpp_omemo_manager.h b/qomemo/qxmpp_omemo_manager.h
index 377c77d..aae7467 100644
--- a/qomemo/qxmpp_omemo_manager.h
+++ b/qomemo/qxmpp_omemo_manager.h
@@ -12,27 +12,27 @@
 
 namespace QXmpp::Omemo {
 
-class Manager : public QXmppClientExtension {
-  Q_OBJECT;
+    class Manager : public QXmppClientExtension {
+    Q_OBJECT;
 
-public:
-  Manager();
-  ~Manager() override = default;
+    public:
+        Manager();
+        ~Manager() override = default;
 
-  bool handleStanza(const QDomElement &stanza) override;
+        bool handleStanza(const QDomElement &stanza) override;
 
-public slots:
-  void fetchOwnDevices();
+    public slots:
+        void fetchOwnDevices();
 
-signals:
-  void deviceListReceived(const QString &jid, const DeviceList &list);
+    signals:
+        void deviceListReceived(const QString &jid, const QXmpp::Omemo::DeviceList &list);
 
-protected:
-  void setClient(QXmppClient *client) override;
+    protected:
+        void setClient(QXmppClient *client) override;
 
-private:
-  QScopedPointer<DeviceService> deviceService;
-  QScopedPointer<Variant::Base> omemoVariant;
-};
+    private:
+        QScopedPointer<DeviceService> deviceService;
+        QScopedPointer<Variant::Base> omemoVariant;
+    };
 
 } // namespace QXmpp::Omemo
diff --git a/qomemo/sce.cpp b/qomemo/sce.cpp
index decb8b3..b94cede 100644
--- a/qomemo/sce.cpp
+++ b/qomemo/sce.cpp
@@ -6,19 +6,21 @@
 
 #include <QRandomGenerator>
 
+#define RPAD_ALPHABET "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
+
 constexpr int RPAD_MAX_LENGTH = 200;
 
 QString QXmpp::Sce::generatePadding() {
-  QRandomGenerator random{};
-  QString result{};
-  QString alphabet{ QStringLiteral("!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~") };
+    QRandomGenerator random{};
+    QString result{};
+    QString alphabet{QStringLiteral(RPAD_ALPHABET)};
 
-  auto length = random.bounded(RPAD_MAX_LENGTH);
-  result.resize(length);
+    auto length = random.bounded(RPAD_MAX_LENGTH);
+    result.resize(length);
 
-  for (auto i = 0; i < length; ++i) {
-    result[i] = alphabet[random.bounded(alphabet.length())];
-  }
+    for (auto i = 0; i < length; ++i) {
+        result[i] = alphabet[random.bounded(alphabet.length())];
+    }
 
-  return result;
+    return result;
 }
diff --git a/qomemo/sce.h b/qomemo/sce.h
index 91a7b2c..497aa3d 100644
--- a/qomemo/sce.h
+++ b/qomemo/sce.h
@@ -9,6 +9,6 @@
 
 namespace QXmpp::Sce {
 
-QString generatePadding();
+    QString generatePadding();
 
 }
diff --git a/qomemo/signal/stores/signed_pre_key_store.cpp b/qomemo/signal/stores/signed_pre_key_store.cpp
index 0156894..43bae1e 100644
--- a/qomemo/signal/stores/signed_pre_key_store.cpp
+++ b/qomemo/signal/stores/signed_pre_key_store.cpp
@@ -35,7 +35,8 @@ int Signal::Store::SignedPreKeyStore::loadSignedPreKey(signal_buffer **record, u
     return 0;
 }
 
-int Signal::Store::SignedPreKeyStore::storeSignedPreKey(uint32_t signed_pre_key_id, uint8_t *record, size_t record_len) {
+int
+Signal::Store::SignedPreKeyStore::storeSignedPreKey(uint32_t signed_pre_key_id, uint8_t *record, size_t record_len) {
     return 0;
 }
 
diff --git a/qomemo/user_device_list.cpp b/qomemo/user_device_list.cpp
index 9a3f18a..5910ed2 100644
--- a/qomemo/user_device_list.cpp
+++ b/qomemo/user_device_list.cpp
@@ -6,5 +6,4 @@
 
 #include <utility>
 
-QXmpp::Omemo::UserDeviceList::UserDeviceList(QString jid)
-    : jid(std::move(jid)) {}
+QXmpp::Omemo::UserDeviceList::UserDeviceList(QString jid) : jid(std::move(jid)) {}
diff --git a/qomemo/user_device_list.h b/qomemo/user_device_list.h
index 27e407a..00465aa 100644
--- a/qomemo/user_device_list.h
+++ b/qomemo/user_device_list.h
@@ -8,11 +8,11 @@
 
 namespace QXmpp::Omemo {
 
-class UserDeviceList {
-public:
-  explicit UserDeviceList(QString jid);
+    class UserDeviceList {
+    public:
+        explicit UserDeviceList(QString jid);
 
-  const QString jid;
-};
+        const QString jid;
+    };
 
 } // namespace QXmpp::Omemo
diff --git a/qomemo/variant/conversations.cpp b/qomemo/variant/conversations.cpp
index b2f85c4..4fc6af6 100644
--- a/qomemo/variant/conversations.cpp
+++ b/qomemo/variant/conversations.cpp
@@ -13,66 +13,66 @@ using namespace QXmpp::Omemo;
 using namespace QXmpp::Factories;
 
 QXmppElement Variant::Conversations::deviceToXml(const Device &device) {
-  auto result = createElement("device");
-  result.setAttribute("id", QString::number(device.id));
+    auto result = createElement("device");
+    result.setAttribute("id", QString::number(device.id));
 
-  return result;
+    return result;
 }
 
 Device Variant::Conversations::deviceFromXml(const QXmppElement &xml) {
-  Device result{};
+    Device result{};
+
+    if (!elementMatches(xml, "device"))
+        return result;
+
+    result.id = xml.attribute("id").toInt();
 
-  if (!elementMatches(xml, "device"))
     return result;
-
-  result.id = xml.attribute("id").toInt();
-
-  return result;
 }
 
 QXmppElement
 Variant::Conversations::deviceListToXml(const DeviceList &deviceList) {
-  auto element = createElement("list", "eu.siacs.conversations.axolotl");
+    auto element = createElement("list", "eu.siacs.conversations.axolotl");
 
-  for (const auto &device : deviceList.devices) {
-    element.appendChild(deviceToXml(device));
-  }
+    for (const auto &device : deviceList.devices) {
+        element.appendChild(deviceToXml(device));
+    }
 
-  return element;
+    return element;
 }
 
 DeviceList Variant::Conversations::deviceListFromXml(const QXmppElement &xml) {
-  DeviceList result{};
+    DeviceList result{};
+
+    if (!elementMatches(xml, "list", "eu.siacs.conversations.axolotl"))
+        return result;
+
+    auto deviceElement = xml.firstChildElement("device");
+    while (!deviceElement.isNull()) {
+        result.devices.push_back(deviceFromXml(deviceElement));
+        deviceElement = deviceElement.nextSiblingElement("device");
+    }
 
-  if (!elementMatches(xml, "list", "eu.siacs.conversations.axolotl"))
     return result;
-
-  auto deviceElement = xml.firstChildElement("device");
-  while (!deviceElement.isNull()) {
-    result.devices.push_back(deviceFromXml(deviceElement));
-    deviceElement = deviceElement.nextSiblingElement("device");
-  }
-
-  return result;
 }
 
 QXmppIq Variant::Conversations::deviceListSetIq(const DeviceList &deviceList) {
-  QXmppIq iq{};
+    QXmppIq iq{};
 
-  iq.setType(QXmppIq::Set);
+    iq.setType(QXmppIq::Set);
 
-  auto item = createElement("item");
-  item.appendChild(deviceListToXml(deviceList));
+    auto item = createElement("item");
+    item.appendChild(deviceListToXml(deviceList));
 
-  auto publish = createElement("publish");
-  publish.setAttribute("node", "eu.siacs.conversations.axolotl.devicelist");
-  publish.appendChild(item);
+    auto publish = createElement("publish");
+    publish.setAttribute("node", "eu.siacs.conversations.axolotl.devicelist");
+    publish.appendChild(item);
 
-  auto pubSub = createElement("pubsub", "http://jabber.org/protocol/pubsub");
-  pubSub.appendChild(publish);
-  pubSub.appendChild(createOpenPublishOptions());
+    auto pubSub = createElement("pubsub", "http://jabber.org/protocol/pubsub");
+    pubSub.appendChild(publish);
+    pubSub.appendChild(createOpenPublishOptions());
 
-  iq.extensions().push_back(pubSub);
+    iq.extensions().push_back(pubSub);
 
-  return iq;
+    return iq;
 }
diff --git a/qomemo/variant/conversations.h b/qomemo/variant/conversations.h
index 917dbcd..923e29b 100644
--- a/qomemo/variant/conversations.h
+++ b/qomemo/variant/conversations.h
@@ -8,16 +8,16 @@
 
 namespace QXmpp::Omemo::Variant {
 
-class Conversations : public Base {
-public:
-  ~Conversations() override = default;
+    class Conversations : public Base {
+    public:
+        ~Conversations() override = default;
 
-  QXmppElement deviceToXml(const Device &device) override;
-  Device deviceFromXml(const QXmppElement &xml) override;
+        QXmppElement deviceToXml(const Device &device) override;
+        Device deviceFromXml(const QXmppElement &xml) override;
 
-  QXmppElement deviceListToXml(const DeviceList &deviceList) override;
-  DeviceList deviceListFromXml(const QXmppElement &xml) override;
-  QXmppIq deviceListSetIq(const DeviceList &deviceList) override;
-};
+        QXmppElement deviceListToXml(const DeviceList &deviceList) override;
+        DeviceList deviceListFromXml(const QXmppElement &xml) override;
+        QXmppIq deviceListSetIq(const DeviceList &deviceList) override;
+    };
 
 } // namespace QXmpp::Omemo::Variant
diff --git a/qomemo/variant/omemo_base.h b/qomemo/variant/omemo_base.h
index fd11f31..58910fe 100644
--- a/qomemo/variant/omemo_base.h
+++ b/qomemo/variant/omemo_base.h
@@ -5,27 +5,29 @@
 #pragma once
 
 class QXmppElement;
+
 class QXmppIq;
 
 namespace QXmpp::Omemo {
 
-class Device;
-class DeviceList;
+    class Device;
 
-namespace Variant {
+    class DeviceList;
 
-class Base {
-public:
-  virtual ~Base() = default;
+    namespace Variant {
 
-  virtual QXmppElement deviceToXml(const Device &device) = 0;
-  virtual Device deviceFromXml(const QXmppElement &xml) = 0;
+        class Base {
+        public:
+            virtual ~Base() = default;
 
-  virtual QXmppElement deviceListToXml(const DeviceList &deviceList) = 0;
-  virtual DeviceList deviceListFromXml(const QXmppElement &xml) = 0;
-  virtual QXmppIq deviceListSetIq(const DeviceList &deviceList) = 0;
-};
+            virtual QXmppElement deviceToXml(const Device &device) = 0;
+            virtual Device deviceFromXml(const QXmppElement &xml) = 0;
 
-} // namespace Variant
+            virtual QXmppElement deviceListToXml(const DeviceList &deviceList) = 0;
+            virtual DeviceList deviceListFromXml(const QXmppElement &xml) = 0;
+            virtual QXmppIq deviceListSetIq(const DeviceList &deviceList) = 0;
+        };
+
+    } // namespace Variant
 
 } // namespace QXmpp::Omemo

From bb2ce750c87aff7cd5846c2c61b28f9794374524 Mon Sep 17 00:00:00 2001
From: vae <vae@programming.socks.town>
Date: Sat, 15 May 2021 00:48:32 +0300
Subject: [PATCH 17/20] build: add OpenSSL dependency

---
 CMakeLists.txt | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index aafc412..9ca13cb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -23,6 +23,7 @@ option(WITH_KIO "Build KIO support module" ON)
 # Dependencies
 ## Qt
 find_package(Qt5 COMPONENTS Widgets DBus Gui Xml Network Core REQUIRED)
+target_link_libraries(squawk PRIVATE Qt5::Core Qt5::Widgets Qt5::DBus Qt5::Network Qt5::Gui Qt5::Xml)
 
 ## QXmpp
 if (SYSTEM_QXMPP)
@@ -81,10 +82,13 @@ endif ()
 
 ## LMDB
 find_package(LMDB REQUIRED)
-
-# Linking
-target_link_libraries(squawk PRIVATE Qt5::Core Qt5::Widgets Qt5::DBus Qt5::Network Qt5::Gui Qt5::Xml)
 target_link_libraries(squawk PRIVATE lmdb)
+
+# OpenSSL
+find_package(OpenSSL REQUIRED)
+target_link_libraries(squawk PRIVATE OpenSSL::Crypto)
+
+# Misc
 target_link_libraries(squawk PRIVATE simpleCrypt)
 target_link_libraries(squawk PRIVATE uuid)
 

From 08fe37bfb283f2f2673a9b11ab8e7ae3cfc70a8f Mon Sep 17 00:00:00 2001
From: vae <vae@programming.socks.town>
Date: Sat, 15 May 2021 12:28:43 +0300
Subject: [PATCH 18/20] feat(omemo): basic UI

---
 ui/omemo/CMakeLists.txt      |  11 ++-
 ui/omemo/contactsettings.cpp |  23 ++++++
 ui/omemo/contactsettings.h   |  24 ++++++
 ui/omemo/contactsettings.ui  | 144 +++++++++++++++++++++++++++++++++++
 ui/omemo/omemodevices.cpp    |   4 +-
 ui/omemo/omemodevices.h      |  10 +--
 ui/widgets/conversation.cpp  |  11 ++-
 ui/widgets/conversation.h    |   1 +
 ui/widgets/conversation.ui   |   7 +-
 9 files changed, 220 insertions(+), 15 deletions(-)
 create mode 100644 ui/omemo/contactsettings.cpp
 create mode 100644 ui/omemo/contactsettings.h
 create mode 100644 ui/omemo/contactsettings.ui

diff --git a/ui/omemo/CMakeLists.txt b/ui/omemo/CMakeLists.txt
index 41afa04..b775e83 100644
--- a/ui/omemo/CMakeLists.txt
+++ b/ui/omemo/CMakeLists.txt
@@ -1,5 +1,8 @@
 target_sources(squawk PRIVATE
-  omemodevices.cpp
-  omemodevices.h
-  omemodevices.ui
-  )
\ No newline at end of file
+        contactsettings.cpp
+        contactsettings.h
+        contactsettings.ui
+        omemodevices.cpp
+        omemodevices.h
+        omemodevices.ui
+        )
\ No newline at end of file
diff --git a/ui/omemo/contactsettings.cpp b/ui/omemo/contactsettings.cpp
new file mode 100644
index 0000000..c4d20b9
--- /dev/null
+++ b/ui/omemo/contactsettings.cpp
@@ -0,0 +1,23 @@
+/*
+ * Created by victoria on 2021-05-15.
+*/
+
+#include "contactsettings.h"
+#include "ui_contactsettings.h"
+#include "omemodevices.h"
+
+ContactSettings::ContactSettings(QWidget *parent)
+        : QDialog(parent), m_ui(new Ui::ContactSettings()) {
+    m_ui->setupUi(this);
+
+    connect(m_ui->devicesButton, &QPushButton::clicked, this, &ContactSettings::openDeviceList);
+}
+
+ContactSettings::~ContactSettings() {}
+
+void ContactSettings::openDeviceList() {
+    auto devices = new OMEMODevices(this);
+
+    devices->setAttribute(Qt::WA_DeleteOnClose);
+    devices->show();
+}
diff --git a/ui/omemo/contactsettings.h b/ui/omemo/contactsettings.h
new file mode 100644
index 0000000..4720c1f
--- /dev/null
+++ b/ui/omemo/contactsettings.h
@@ -0,0 +1,24 @@
+/*
+ * Created by victoria on 2021-05-15.
+*/
+
+#pragma once
+
+#include <QDialog>
+
+namespace Ui {
+    class ContactSettings;
+}
+
+class ContactSettings : public QDialog {
+    Q_OBJECT
+public:
+    explicit ContactSettings(QWidget *parent = nullptr);
+    ~ContactSettings() override;
+
+private slots:
+    void openDeviceList();
+
+private:
+    QScopedPointer<Ui::ContactSettings> m_ui;
+};
diff --git a/ui/omemo/contactsettings.ui b/ui/omemo/contactsettings.ui
new file mode 100644
index 0000000..5f7e77a
--- /dev/null
+++ b/ui/omemo/contactsettings.ui
@@ -0,0 +1,144 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ContactSettings</class>
+ <widget class="QDialog" name="ContactSettings">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>400</width>
+    <height>300</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Contact Settings</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QLabel" name="label">
+     <property name="text">
+      <string>Settings for foo@example.com</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QFrame" name="frame">
+     <property name="frameShape">
+      <enum>QFrame::StyledPanel</enum>
+     </property>
+     <property name="frameShadow">
+      <enum>QFrame::Raised</enum>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout_2">
+      <item>
+       <widget class="QWidget" name="widget" native="true">
+        <layout class="QHBoxLayout" name="horizontalLayout">
+         <property name="spacing">
+          <number>0</number>
+         </property>
+         <property name="leftMargin">
+          <number>0</number>
+         </property>
+         <property name="topMargin">
+          <number>0</number>
+         </property>
+         <property name="rightMargin">
+          <number>0</number>
+         </property>
+         <property name="bottomMargin">
+          <number>0</number>
+         </property>
+         <item>
+          <widget class="QCheckBox" name="useOmemoCheckBox">
+           <property name="text">
+            <string>Use OMEMO</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <spacer name="horizontalSpacer">
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>40</width>
+             <height>20</height>
+            </size>
+           </property>
+          </spacer>
+         </item>
+         <item>
+          <widget class="QPushButton" name="devicesButton">
+           <property name="text">
+            <string>Devices...</string>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </widget>
+      </item>
+      <item>
+       <spacer name="verticalSpacer">
+        <property name="orientation">
+         <enum>Qt::Vertical</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>20</width>
+          <height>40</height>
+         </size>
+        </property>
+       </spacer>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>ContactSettings</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>ContactSettings</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
diff --git a/ui/omemo/omemodevices.cpp b/ui/omemo/omemodevices.cpp
index e746f5b..877ff27 100644
--- a/ui/omemo/omemodevices.cpp
+++ b/ui/omemo/omemodevices.cpp
@@ -6,8 +6,8 @@
 #include "ui_omemodevices.h"
 
 OMEMODevices::OMEMODevices(QWidget *parent)
-    : QDialog(parent), m_ui(new Ui::OMEMODevices()) {
-  m_ui->setupUi(this);
+        : QDialog(parent), m_ui(new Ui::OMEMODevices()) {
+    m_ui->setupUi(this);
 }
 
 OMEMODevices::~OMEMODevices() {}
diff --git a/ui/omemo/omemodevices.h b/ui/omemo/omemodevices.h
index b107baa..70dbb0f 100644
--- a/ui/omemo/omemodevices.h
+++ b/ui/omemo/omemodevices.h
@@ -7,15 +7,15 @@
 #include <QDialog>
 
 namespace Ui {
-class OMEMODevices;
+    class OMEMODevices;
 }
 
 class OMEMODevices : public QDialog {
-  Q_OBJECT
+    Q_OBJECT
 public:
-  explicit OMEMODevices(QWidget *parent = nullptr);
-  ~OMEMODevices() override;
+    explicit OMEMODevices(QWidget *parent = nullptr);
+    ~OMEMODevices() override;
 
 private:
-  QScopedPointer<Ui::OMEMODevices> m_ui;
+    QScopedPointer<Ui::OMEMODevices> m_ui;
 };
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index 45ce2c5..2b2547b 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -27,6 +27,7 @@
 #include <unistd.h>
 #include <QAbstractTextDocumentLayout>
 #include <QCoreApplication>
+#include <ui/omemo/contactsettings.h>
 
 Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el, const QString pJid, const QString pRes, QWidget* parent):
     QWidget(parent),
@@ -80,7 +81,8 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
     connect(m_ui->clearButton, &QPushButton::clicked, this, &Conversation::onClearButton);
     connect(m_ui->messageEditor->document()->documentLayout(), &QAbstractTextDocumentLayout::documentSizeChanged, 
             this, &Conversation::onTextEditDocSizeChanged);
-    
+    connect(m_ui->encryptionButton, &QToolButton::clicked, this, &Conversation::openEncryptionSettings);
+
     m_ui->messageEditor->installEventFilter(&ker);
     
     
@@ -433,3 +435,10 @@ void Conversation::onFeedContext(const QPoint& pos)
         }
     }
 }
+
+void Conversation::openEncryptionSettings() {
+    auto cs = new ContactSettings(this);
+
+    cs->setAttribute(Qt::WA_DeleteOnClose);
+    cs->show();
+}
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index 0b0dcb2..5ec7acc 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -107,6 +107,7 @@ protected slots:
     void onFeedMessage(const Shared::Message& msg);
     void positionShadow();
     void onFeedContext(const QPoint &pos);
+    void openEncryptionSettings();
     
 public:
     const bool isMuc;
diff --git a/ui/widgets/conversation.ui b/ui/widgets/conversation.ui
index 36b74a4..e9bcbf8 100644
--- a/ui/widgets/conversation.ui
+++ b/ui/widgets/conversation.ui
@@ -293,14 +293,15 @@
           </spacer>
          </item>
          <item>
-          <widget class="QPushButton" name="encryptionButton">
+          <widget class="QToolButton" name="encryptionButton">
            <property name="text">
             <string/>
            </property>
            <property name="icon">
-            <iconset theme="security-low"/>
+            <iconset theme="security-low">
+             <normaloff>.</normaloff>.</iconset>
            </property>
-           <property name="flat">
+           <property name="autoRaise">
             <bool>true</bool>
            </property>
           </widget>

From 442ad37300d3112ece31a005ba4689f21957ae9c Mon Sep 17 00:00:00 2001
From: vae <vae@programming.socks.town>
Date: Thu, 22 Jul 2021 20:45:39 +0300
Subject: [PATCH 19/20] feat: omemo signal lib wip

---
 qomemo/CMakeLists.txt                       |   2 +
 qomemo/bundle.cpp                           | 124 ----------
 qomemo/bundle.h                             |  19 +-
 qomemo/database.cpp                         |  66 +++++-
 qomemo/database.h                           |  28 ++-
 qomemo/device_service.cpp                   |  24 +-
 qomemo/device_service.h                     |  11 +-
 qomemo/key.cpp                              |   6 +
 qomemo/key.h                                |  27 +++
 qomemo/qxmpp_omemo_manager.cpp              | 236 ++++++++++++++++++--
 qomemo/qxmpp_omemo_manager.h                |  25 ++-
 qomemo/signal/CMakeLists.txt                |   2 +
 qomemo/signal/context.cpp                   |  17 +-
 qomemo/signal/context.h                     |   8 +
 qomemo/signal/crypto/CMakeLists.txt         |   2 +
 qomemo/signal/crypto/ec.cpp                 |   5 +
 qomemo/signal/crypto/ec.h                   |  20 ++
 qomemo/signal/stores/identity_key_store.cpp |  73 +++---
 qomemo/signal/stores/identity_key_store.h   |  13 +-
 qomemo/signal/stores/pre_key_store.cpp      |  41 ++--
 qomemo/signal/stores/pre_key_store.h        |   9 +-
 qomemo/signal/util.cpp                      |  14 ++
 qomemo/signal/util.h                        |  18 ++
 qomemo/variant/conversations.cpp            | 139 +++++++++++-
 qomemo/variant/conversations.h              |  11 +-
 qomemo/variant/omemo_base.h                 |  18 +-
 ui/omemo/contactsettings.cpp                |   8 +-
 ui/omemo/contactsettings.h                  |   4 +-
 ui/omemo/omemodevices.cpp                   |   6 +-
 ui/omemo/omemodevices.h                     |   4 +-
 ui/squawk.cpp                               |  10 +-
 ui/squawk.h                                 |   2 +-
 ui/squawk.ui                                |  13 +-
 ui/widgets/conversation.cpp                 |   2 +-
 34 files changed, 745 insertions(+), 262 deletions(-)
 create mode 100644 qomemo/key.cpp
 create mode 100644 qomemo/key.h
 create mode 100644 qomemo/signal/crypto/ec.cpp
 create mode 100644 qomemo/signal/crypto/ec.h
 create mode 100644 qomemo/signal/util.cpp
 create mode 100644 qomemo/signal/util.h

diff --git a/qomemo/CMakeLists.txt b/qomemo/CMakeLists.txt
index 8b194be..7c71af1 100644
--- a/qomemo/CMakeLists.txt
+++ b/qomemo/CMakeLists.txt
@@ -9,6 +9,8 @@ target_sources(squawk PRIVATE
         device_key_storage.h
         device_service.cpp
         device_service.h
+        key.cpp
+        key.h
         qomemo.cpp
         qomemo.h
         sce.cpp
diff --git a/qomemo/bundle.cpp b/qomemo/bundle.cpp
index 691a65e..d27cd1e 100644
--- a/qomemo/bundle.cpp
+++ b/qomemo/bundle.cpp
@@ -3,127 +3,3 @@
  */
 
 #include "bundle.h"
-
-#include <QXmppPubSubIq.h>
-
-#include <QDebug>
-
-#include "shared/qxmppfactories.h"
-
-using namespace QXmpp::Factories;
-
-QXmppElement QXmpp::Omemo::PreKey::toXml() const {
-    auto pk = createElement("preKeyPublic");
-    pk.setAttribute("preKeyId", QString::number(id));
-    // TODO: Base64
-    pk.setValue(data);
-
-    return pk;
-}
-
-void QXmpp::Omemo::PreKey::fromXml(const QXmppElement &element) {
-    if (!elementMatches(element, "preKeyPublic"))
-        return;
-
-    id = element.attribute("preKeyId").toInt();
-    // TODO: Base64
-    data = element.value();
-}
-
-QXmppElement QXmpp::Omemo::Bundle::toXml() const {
-    auto spkNode = createElement("signedPreKeyPublic");
-    spkNode.setAttribute("signedPreKeyId", QString::number(spkId));
-    spkNode.setValue(spk);
-
-    auto spksNode = createElement("signedPreKeySignature");
-    spksNode.setValue(spks);
-
-    auto ikNode = createElement("identityKey");
-    ikNode.setValue(ik);
-
-    auto prekeysNode = createElement("prekeys");
-    for (const auto &pk : prekeys) {
-        prekeysNode.appendChild(pk.toXml());
-    }
-
-    auto result = createElement("bundle", "eu.siacs.conversations.axolotl");
-    result.appendChild(spkNode);
-    result.appendChild(spksNode);
-    result.appendChild(ikNode);
-    result.appendChild(prekeysNode);
-
-    return result;
-}
-
-QXmppIq QXmpp::Omemo::Bundle::toIq(int deviceId) const {
-    QXmppIq iq{};
-
-    iq.setType(QXmppIq::Set);
-
-    auto item = createElement("item");
-    item.appendChild(toXml());
-
-    auto publish = createElement("publish");
-    publish.setAttribute(
-            "node",
-            QString("eu.siacs.conversations.axolotl.bundles:%s").arg(deviceId));
-    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 QXmpp::Omemo::Bundle::fromXml(const QXmppElement &element) {
-    if (!elementMatches(element, "bundle", "eu.siacs.conversations.axolotl"))
-        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);
-    }
-}
-
-QXmppPubSubIq QXmpp::Omemo::Bundle::fetchDeviceBundleIq(int deviceId) {
-    QXmppPubSubIq iq{};
-    iq.setType(QXmppIq::Get);
-    iq.setQueryNode(
-            QString("eu.siacs.conversations.axolotl.bundles:%1").arg(deviceId));
-
-    QXmppPubSubItem item{};
-    item.setId(QString::number(deviceId));
-    iq.setItems({item});
-
-    return iq;
-}
\ No newline at end of file
diff --git a/qomemo/bundle.h b/qomemo/bundle.h
index 428277b..61e8185 100644
--- a/qomemo/bundle.h
+++ b/qomemo/bundle.h
@@ -6,6 +6,7 @@
 
 #include <QList>
 #include <QString>
+#include <QBuffer>
 
 class QXmppPubSubIq;
 
@@ -17,26 +18,16 @@ namespace QXmpp::Omemo {
 
     class PreKey {
     public:
-        [[nodiscard]] QXmppElement toXml() const;
-        /// Expects a <pk>
-        void fromXml(const QXmppElement &element);
-
         int id;
-        QString data;
+        QByteArray data;
     };
 
     class Bundle {
     public:
-        [[nodiscard]] static QXmppPubSubIq fetchDeviceBundleIq(int deviceId);
-
-        [[nodiscard]] QXmppElement toXml() const;
-        [[nodiscard]] QXmppIq toIq(int deviceId) const;
-        void fromXml(const QXmppElement &element);
-
-        QString spk;
+        QByteArray spk;
         int spkId;
-        QString spks;
-        QString ik;
+        QByteArray spks;
+        QByteArray ik;
         QList<PreKey> prekeys;
     };
 
diff --git a/qomemo/database.cpp b/qomemo/database.cpp
index 2314a80..2923634 100644
--- a/qomemo/database.cpp
+++ b/qomemo/database.cpp
@@ -4,6 +4,8 @@
 
 #include "database.h"
 
+#include "bundle.h"
+
 #include <QDebug>
 #include <QDir>
 #include <QException>
@@ -44,17 +46,17 @@ Database::~Database() {
     mdb_env_close(env);
 }
 
-QBuffer Database::loadIdentityKeySecret(int deviceId) { return QBuffer(); }
+std::optional<QByteArray> Database::loadIdentityKeySecret(int deviceId) { return std::nullopt; }
 
 bool Database::saveIdentityKeySecret(int deviceId,
-                                     const QBuffer &identityKeySecret) {
+                                     const QByteArray &identityKeySecret) {
     MDB_val mdbKey, mdbValue;
     auto key = QString("%1/secret").arg(QString::number(deviceId)).toStdString();
 
     mdbKey.mv_data = key.data();
     mdbKey.mv_size = key.size();
 
-    mdbValue.mv_data = const_cast<char *>(identityKeySecret.data().data());
+    mdbValue.mv_data = const_cast<char *>(identityKeySecret.data());
     mdbValue.mv_size = identityKeySecret.size();
 
     MDB_txn *txn;
@@ -71,7 +73,7 @@ bool Database::saveIdentityKeySecret(int deviceId,
     return false;
 }
 
-int Database::loadActiveDeviceId() {
+std::optional<int> Database::loadActiveDeviceId() {
     MDB_val key, value;
 
     key.mv_data = (void *) "active";
@@ -83,12 +85,14 @@ int Database::loadActiveDeviceId() {
     auto err = mdb_get(txn, dbiIdentityKeys, &key, &value);
     if (err) {
         qWarning() << "could not load active device id:" << mdb_strerror(err);
-        return 0;
+        mdb_txn_abort(txn);
+        return std::nullopt;
     }
 
     if (value.mv_size != sizeof(int)) {
         qWarning() << "mv_size is" << value.mv_size << "instead of" << sizeof(int);
-        return 0;
+        mdb_txn_abort(txn);
+        return std::nullopt;
     }
 
     auto id = *reinterpret_cast<int *>(value.mv_data);
@@ -124,3 +128,53 @@ bool Database::saveActiveDeviceId(int deviceId) {
 
     return true;
 }
+
+bool Database::saveIdentityKey(int deviceId, const QByteArray &identityKey) {
+    return false;
+}
+
+std::optional<QByteArray> Database::loadIdentityKey(int deviceId) {
+    return std::nullopt;
+}
+
+bool Database::containsPreKey() {
+    return false;
+}
+
+std::optional<KeyPair> Database::loadPreKey(int deviceId, int id) {
+    return std::nullopt;
+}
+
+bool Database::savePreKey(int deviceId, int id, const KeyPair &preKey) {
+    return false;
+}
+
+std::optional<Bundle> Database::loadBundle(int deviceId) {
+    Bundle result{};
+
+    auto ik = loadIdentityKey(deviceId);
+
+    result.ik = ik.value();
+
+    auto spk = loadSignedPreKey(deviceId);
+
+    result.spk = spk->key.publicKey;
+    result.spks = spk->signature;
+    result.spkId = spk->id;
+
+    // PreKeys
+
+    return result;
+}
+
+bool Database::saveBundle(int deviceId, const Bundle &bundle) {
+    return false;
+}
+
+std::optional<SignedPreKey> Database::loadSignedPreKey(int deviceId) {
+    return std::optional<SignedPreKey>();
+}
+
+bool Database::saveSignedPreKey(int deviceId, const SignedPreKey &signedPreKey) {
+    return false;
+}
diff --git a/qomemo/database.h b/qomemo/database.h
index 3f9b933..1edeea6 100644
--- a/qomemo/database.h
+++ b/qomemo/database.h
@@ -8,8 +8,12 @@
 #include <QString>
 #include <lmdb.h>
 
+#include "key.h"
+
 namespace QXmpp::Omemo {
 
+    class Bundle;
+
     class Database {
     public:
         explicit Database(QString jid);
@@ -18,14 +22,26 @@ namespace QXmpp::Omemo {
         Database(Database &&) = delete;
         Database &operator=(const Database &) = delete;
 
-        QBuffer loadIdentityKey();
-        bool saveIdentityKey(const QBuffer &identityKey);
-
-        int loadActiveDeviceId();
+        // For local user
+        std::optional<int> loadActiveDeviceId();
         bool saveActiveDeviceId(int deviceId);
 
-        QBuffer loadIdentityKeySecret(int deviceId);
-        bool saveIdentityKeySecret(int deviceId, const QBuffer &identityKeySecret);
+        std::optional<QByteArray> loadIdentityKeySecret(int deviceId);
+        bool saveIdentityKeySecret(int deviceId, const QByteArray &identityKeySecret);
+
+        std::optional<Bundle> loadBundle(int deviceId);
+        bool saveBundle(int deviceId, const Bundle& bundle);
+
+        // For any user
+        std::optional<QByteArray> loadIdentityKey(int deviceId);
+        bool saveIdentityKey(int deviceId, const QByteArray &identityKey);
+
+        bool containsPreKey();
+        std::optional<KeyPair> loadPreKey(int deviceId, int id);
+        bool savePreKey(int deviceId, int id, const KeyPair &preKey);
+
+        std::optional<SignedPreKey> loadSignedPreKey(int deviceId);
+        bool saveSignedPreKey(int deviceId, const SignedPreKey& signedPreKey);
 
         const QString jid;
 
diff --git a/qomemo/device_service.cpp b/qomemo/device_service.cpp
index 306f383..653a3a6 100644
--- a/qomemo/device_service.cpp
+++ b/qomemo/device_service.cpp
@@ -5,11 +5,31 @@
 #include "device_service.h"
 #include "device.h"
 
-QXmpp::Omemo::DeviceService::DeviceService(QObject *parent) : QObject(parent) {}
+using namespace QXmpp::Omemo;
 
-void QXmpp::Omemo::DeviceService::onDeviceListReceived(const QString &jid, const QXmpp::Omemo::DeviceList &list) {
+DeviceService::DeviceService(QObject *parent) : QObject(parent) {}
+
+void DeviceService::onDeviceListReceived(const QString &jid, const QXmpp::Omemo::DeviceList &list) {
 
     for (const auto &device : list.devices) {
         qInfo() << "Got device for" << jid << ":" << device.id;
     }
 }
+
+QSharedPointer<Database> DeviceService::getDatabase(const QString &jid) {
+    if (!databases.contains(jid)) {
+        databases.insert(jid, QSharedPointer<Database>::create(jid));
+    }
+
+    return databases[jid];
+}
+
+void DeviceService::addIdentity(const QString &jid, int deviceId, const QByteArray& publicKey) {
+    auto db = getDatabase(jid);
+
+    db->saveIdentityKey(deviceId, publicKey);
+}
+
+void DeviceService::onDeviceListNotFound(const QString &jid) {
+    qInfo() << "Device list not found:" << jid;
+}
diff --git a/qomemo/device_service.h b/qomemo/device_service.h
index a9529c4..2442486 100644
--- a/qomemo/device_service.h
+++ b/qomemo/device_service.h
@@ -4,8 +4,11 @@
 
 #pragma once
 
+#include <QBuffer>
+
 #include "qomemo.h"
 #include "user_device_list.h"
+#include "database.h"
 
 #include <QXmppClient.h>
 
@@ -19,11 +22,17 @@ namespace QXmpp::Omemo {
     public:
         explicit DeviceService(QObject *parent);
 
+        QSharedPointer<Database> getDatabase(const QString& jid);
+
     public slots:
+        void addIdentity(const QString& jid, int deviceId, const QByteArray& publicKey);
+
         void onDeviceListReceived(const QString &jid, const QXmpp::Omemo::DeviceList &list);
 
+        void onDeviceListNotFound(const QString &jid);
+
     private:
-        QMap<QString, UserDeviceList> device_lists{};
+        QMap<QString, QSharedPointer<Database>> databases{};
     };
 
 } // namespace QXmpp::Omemo
diff --git a/qomemo/key.cpp b/qomemo/key.cpp
new file mode 100644
index 0000000..7e9cc30
--- /dev/null
+++ b/qomemo/key.cpp
@@ -0,0 +1,6 @@
+/*
+ * Created by victoria on 2021-05-15.
+*/
+
+#include "key.h"
+ 
\ No newline at end of file
diff --git a/qomemo/key.h b/qomemo/key.h
new file mode 100644
index 0000000..a0910c7
--- /dev/null
+++ b/qomemo/key.h
@@ -0,0 +1,27 @@
+/*
+ * Created by victoria on 2021-05-15.
+*/
+
+#pragma once
+
+#include <optional>
+
+#include <QByteArray>
+
+namespace QXmpp::Omemo {
+
+    class KeyPair {
+    public:
+        QByteArray publicKey{};
+        std::optional<QByteArray> secretKey{ std::nullopt };
+    };
+
+    class SignedPreKey {
+    public:
+        int id{ 0 };
+
+        KeyPair key{};
+        QByteArray signature{};
+    };
+
+}
diff --git a/qomemo/qxmpp_omemo_manager.cpp b/qomemo/qxmpp_omemo_manager.cpp
index 844a189..788fc96 100644
--- a/qomemo/qxmpp_omemo_manager.cpp
+++ b/qomemo/qxmpp_omemo_manager.cpp
@@ -4,6 +4,9 @@
 
 #include "qxmpp_omemo_manager.h"
 
+#include "qomemo/signal/context.h"
+
+#include "bundle.h"
 #include "device.h"
 #include "variant/conversations.h"
 
@@ -12,13 +15,24 @@
 #include <QXmppClient.h>
 #include <QXmppPubSubIq.h>
 #include <iostream>
+#include <QRandomGenerator64>
+#include <external/signal-protocol-c/src/signal_protocol_internal.h>
 
 using namespace QXmpp::Omemo;
 
 Manager::Manager()
-        : deviceService(new DeviceService(this)),
+        : deviceService{new DeviceService(this)},
           omemoVariant(new Variant::Conversations) {
     connect(this, &Manager::deviceListReceived, deviceService.get(), &DeviceService::onDeviceListReceived);
+    connect(this, &Manager::deviceListNotFound, deviceService.get(), &DeviceService::onDeviceListNotFound);
+    connect(this, &Manager::deviceListNotFound, this, [this](const QString &jid) {
+        if (jid == client()->configuration().jidBare())
+            generateDeviceListForSelf();
+    });
+    connect(this, &Manager::deviceListReceived, this, [this](const QString &jid, const DeviceList &currentList) {
+        if (jid == client()->configuration().jidBare())
+            generateDeviceForSelfIfNeeded(currentList);
+    });
 }
 
 bool QXmpp::Omemo::Manager::handleStanza(const QDomElement &stanza) {
@@ -28,31 +42,62 @@ bool QXmpp::Omemo::Manager::handleStanza(const QDomElement &stanza) {
 
     std::cout << str.toStdString();
 
-    if (stanza.tagName() == "iq") {
-        if (stanza.attribute("type") == "result") {
-            auto pubsub = stanza.firstChildElement("pubsub");
-            if (!pubsub.isNull()) {
-                auto items = pubsub.firstChildElement("items");
-                if (items.attribute("node") ==
-                    "eu.siacs.conversations.axolotl.devicelist") {
-                    auto item = items.firstChildElement("item");
-                    if (!item.isNull()) {
-                        auto list = item.firstChildElement("list");
-                        if (!list.isNull()) {
-                            DeviceList deviceList = omemoVariant->deviceListFromXml(list);
-                            emit deviceListReceived(stanza.attribute("from"), deviceList);
-
-                            return true;
-                        }
-                    }
-                }
-            }
-        }
-    }
+    if (handleDeviceList(stanza) || handleMissingDeviceList(stanza))
+        return true;
 
     return false;
 }
 
+bool Manager::handleDeviceList(const QDomElement &stanza) {
+    if (!(stanza.tagName() == "iq"))
+        return false;
+
+    if (!(stanza.attribute("type") == "result"))
+        return false;
+
+    auto pubsub = stanza.firstChildElement("pubsub");
+    if (pubsub.isNull())
+        return false;
+
+    auto items = pubsub.firstChildElement("items");
+    if (!(items.attribute("node") == omemoVariant->getDeviceListNode()))
+        return false;
+
+    auto deviceList = omemoVariant->latestDeviceListFromPubSubNode(items);
+    if (!deviceList.has_value())
+        return false;
+
+    emit deviceListReceived(stanza.attribute("from"), deviceList.value());
+
+    return true;
+}
+
+bool Manager::handleMissingDeviceList(const QDomElement &stanza) {
+    if (stanza.tagName() != "iq")
+        return false;
+
+    if (stanza.attribute("type") != "error")
+        return false;
+
+    auto pubsub = stanza.firstChildElement("pubsub");
+    if (pubsub.isNull())
+        return false;
+
+    auto items = pubsub.firstChildElement("items");
+    if (items.attribute("node") != omemoVariant->getDeviceListNode())
+        return false;
+
+    auto error = stanza.firstChildElement("error");
+
+    if (error.namespaceURI() != "jabber:client" || error.attribute("code") != "404")
+        return false;
+
+    qDebug() << "Got 404 deviceList for" << stanza.attribute("from");
+    emit deviceListNotFound(stanza.attribute("from"));
+
+    return true;
+}
+
 void QXmpp::Omemo::Manager::setClient(QXmppClient *client) {
     QXmppClientExtension::setClient(client);
 
@@ -68,7 +113,152 @@ void QXmpp::Omemo::Manager::fetchOwnDevices() {
     iq.setFrom(client()->configuration().jid());
     iq.setTo(client()->configuration().jidBare());
     iq.setType(QXmppIq::Get);
-    iq.setQueryNode("eu.siacs.conversations.axolotl.devicelist");
+    iq.setQueryNode(omemoVariant->getDeviceListNode());
 
     client()->sendPacket(iq);
 }
+
+QSharedPointer<DeviceService> Manager::getDeviceService() {
+    return deviceService;
+}
+
+void Manager::publishDeviceList(const DeviceList &deviceList) {
+//    QXmppPubSubIq iq{};
+//    iq.setFrom(client()->configuration().jid());
+//    iq.setType(QXmppIq::Set);
+//    iq.setQueryNode(omemoVariant->getDeviceListNode());
+//    iq.setItems()
+
+    auto iq = omemoVariant->deviceListSetIq(deviceList);
+    iq.setFrom(client()->configuration().jid());
+
+    QString str{};
+    QXmlStreamWriter info(&str);
+    iq.toXml(&info);
+    qInfo() << str;
+
+    client()->sendPacket(iq);
+}
+
+void Manager::generateDeviceListForSelf() {
+    qInfo() << "Generate device for self...";
+
+    generateDeviceForSelfIfNeeded(DeviceList());
+}
+
+void Manager::publishBundle(int deviceId, const Bundle &bundle) {
+    auto iq = omemoVariant->bundleSetIq(deviceId, bundle);
+    iq.setFrom(client()->configuration().jid());
+
+    QString str{};
+    QXmlStreamWriter info(&str);
+    iq.toXml(&info);
+    qInfo() << str;
+
+    client()->sendPacket(iq);
+}
+
+void Manager::generateDeviceForSelfIfNeeded(const DeviceList &currentList) {
+    auto db = deviceService->getDatabase(client()->configuration().jidBare());
+    auto activeId = db->loadActiveDeviceId();
+
+    if (activeId.has_value()) {
+        qInfo() << "Current device:" << *activeId;
+
+        bool found = false;
+        for (const auto &d : currentList.devices)
+            if (d.id == *activeId)
+                found = true;
+
+        if (found)
+            return;
+
+        qInfo() << "Could not find device" << *activeId << ", generating new one";
+    }
+
+    qInfo() << "Generating device";
+
+    auto deviceId = QRandomGenerator64{}.bounded(INT32_MAX);
+    db->saveActiveDeviceId(deviceId);
+
+    Device device{};
+    device.id = deviceId;
+
+    auto updatedList = currentList;
+
+    auto bundle = Bundle();
+
+    updatedList.devices.push_back(device);
+
+    publishDeviceList(updatedList);
+    publishBundle(deviceId, bundle);
+}
+
+Bundle Manager::generateAndSaveBundle(int deviceId) {
+    auto database = deviceService->getDatabase(client()->configuration().jidBare());
+    Bundle result{};
+
+    ec_key_pair *ecKeyPair;
+    curve_generate_key_pair(signalContext->temporaryGetContextUnsafeForRawAccessThatNeedsToBeWrapped(), &ecKeyPair);
+
+    signal_buffer *ecPublic;
+    ec_public_key_serialize(&ecPublic, ec_key_pair_get_public(ecKeyPair));
+
+    signal_buffer *ecSecret;
+    ec_private_key_serialize(&ecSecret, ec_key_pair_get_private(ecKeyPair));
+
+    QByteArray identityKey((const char *) signal_buffer_const_data(ecPublic), (int) signal_buffer_len(ecPublic));
+    QByteArray identityKeySecret((const char *) signal_buffer_const_data(ecSecret), (int) signal_buffer_len(ecSecret));
+
+    database->saveIdentityKey(deviceId, identityKey);
+    database->saveIdentityKeySecret(deviceId, identityKeySecret);
+
+    // Generate SPK
+    ec_key_pair *ecSpk;
+    curve_generate_key_pair(signalContext->temporaryGetContextUnsafeForRawAccessThatNeedsToBeWrapped(), &ecSpk);
+    signal_buffer *spkPublic, *spkSecret;
+    ec_public_key_serialize(&spkPublic, ec_key_pair_get_public(ecSpk));
+    ec_private_key_serialize(&spkSecret, ec_key_pair_get_private(ecSpk));
+
+    // Generate SPKs
+    signal_buffer *signature;
+    curve_calculate_signature(signalContext->temporaryGetContextUnsafeForRawAccessThatNeedsToBeWrapped(), &signature,
+                              ec_key_pair_get_private(ecKeyPair), signal_buffer_const_data(spkPublic),
+                              signal_buffer_len(spkPublic));
+
+    SignedPreKey spk{};
+    spk.id = 0;
+    spk.key.publicKey = QByteArray((const char *) signal_buffer_const_data(spkPublic), (int) signal_buffer_len(spkPublic));
+    spk.key.secretKey = QByteArray((const char *) signal_buffer_const_data(spkSecret), (int) signal_buffer_len(spkSecret));
+    spk.signature = QByteArray((const char *) signal_buffer_const_data(signature), (int) signal_buffer_len(signature));
+
+    // Generate 100 PK
+    for (auto i = 1; i <= 100; ++i) {
+        ec_key_pair *currentPreKey;
+        curve_generate_key_pair(signalContext->temporaryGetContextUnsafeForRawAccessThatNeedsToBeWrapped(), &currentPreKey);
+
+        signal_buffer *pkPublic, *pkSecret;
+        ec_public_key_serialize(&pkPublic, ec_key_pair_get_public(currentPreKey));
+        ec_private_key_serialize(&pkSecret, ec_key_pair_get_private(currentPreKey));
+
+        KeyPair preKey{};
+        preKey.publicKey = QByteArray((const char *) signal_buffer_const_data(pkPublic), (int) signal_buffer_len(pkPublic));
+        preKey.secretKey = QByteArray((const char *) signal_buffer_const_data(pkSecret), (int) signal_buffer_len(pkSecret));
+
+        database->savePreKey(deviceId, i, preKey);
+
+        signal_buffer_free(pkPublic);
+        signal_buffer_free(pkSecret);
+        SIGNAL_UNREF(currentPreKey);
+    }
+
+    signal_buffer_free(signature);
+
+    signal_buffer_free(ecPublic);
+    signal_buffer_free(ecSecret);
+    SIGNAL_UNREF(ecKeyPair);
+
+    signal_buffer_free(spkPublic);
+    signal_buffer_free(spkSecret);
+    SIGNAL_UNREF(ecSpk);
+}
diff --git a/qomemo/qxmpp_omemo_manager.h b/qomemo/qxmpp_omemo_manager.h
index aae7467..7f04026 100644
--- a/qomemo/qxmpp_omemo_manager.h
+++ b/qomemo/qxmpp_omemo_manager.h
@@ -10,6 +10,10 @@
 
 #include <QXmppClientExtension.h>
 
+namespace Signal {
+    class Context;
+}
+
 namespace QXmpp::Omemo {
 
     class Manager : public QXmppClientExtension {
@@ -21,18 +25,37 @@ namespace QXmpp::Omemo {
 
         bool handleStanza(const QDomElement &stanza) override;
 
+        bool handleDeviceList(const QDomElement& stanza);
+
+        bool handleMissingDeviceList(const QDomElement& stanza);
+
+        QSharedPointer<DeviceService> getDeviceService();
+
+        Bundle generateAndSaveBundle(int deviceId);
+
     public slots:
         void fetchOwnDevices();
 
+        void publishDeviceList(const QXmpp::Omemo::DeviceList& deviceList);
+
+        void generateDeviceListForSelf();
+
+        void generateDeviceForSelfIfNeeded(const QXmpp::Omemo::DeviceList &currentList);
+
+        void publishBundle(int deviceId, const QXmpp::Omemo::Bundle& bundle);
+
     signals:
         void deviceListReceived(const QString &jid, const QXmpp::Omemo::DeviceList &list);
 
+        void deviceListNotFound(const QString &jid);
+
     protected:
         void setClient(QXmppClient *client) override;
 
     private:
-        QScopedPointer<DeviceService> deviceService;
+        QSharedPointer<DeviceService> deviceService;
         QScopedPointer<Variant::Base> omemoVariant;
+        QScopedPointer<Signal::Context> signalContext;
     };
 
 } // namespace QXmpp::Omemo
diff --git a/qomemo/signal/CMakeLists.txt b/qomemo/signal/CMakeLists.txt
index 60cfdd8..7223904 100644
--- a/qomemo/signal/CMakeLists.txt
+++ b/qomemo/signal/CMakeLists.txt
@@ -1,6 +1,8 @@
 target_sources(squawk PRIVATE
         context.cpp
         context.h
+        util.cpp
+        util.h
         )
 
 add_subdirectory(crypto)
diff --git a/qomemo/signal/context.cpp b/qomemo/signal/context.cpp
index bc2322e..6253faa 100644
--- a/qomemo/signal/context.cpp
+++ b/qomemo/signal/context.cpp
@@ -4,6 +4,19 @@
 
 #include "context.h"
 
-Signal::Context::Context() {}
+using namespace Signal;
 
-Signal::Context::~Context() {}
+Context::Context() {}
+
+Context::~Context() {}
+
+std::unique_ptr<Crypto::ECKeyPair> Context::generateCurveKeyPair() {
+    auto result = std::unique_ptr<Crypto::ECKeyPair>();
+    // TODO
+
+    return result;
+}
+
+signal_context *Context::temporaryGetContextUnsafeForRawAccessThatNeedsToBeWrapped() {
+    return ctx;
+}
diff --git a/qomemo/signal/context.h b/qomemo/signal/context.h
index c398105..b829aca 100644
--- a/qomemo/signal/context.h
+++ b/qomemo/signal/context.h
@@ -4,6 +4,10 @@
 
 #pragma once
 
+#include "crypto/ec.h"
+
+#include <memory>
+
 #include <signal/signal_protocol.h>
 
 namespace Signal {
@@ -16,6 +20,10 @@ namespace Signal {
         Context(Context &&) = delete;
         Context &operator=(const Context &) = delete;
 
+        std::unique_ptr<Crypto::ECKeyPair> generateCurveKeyPair();
+
+        signal_context *temporaryGetContextUnsafeForRawAccessThatNeedsToBeWrapped();
+
     private:
         signal_context *ctx{nullptr};
     };
diff --git a/qomemo/signal/crypto/CMakeLists.txt b/qomemo/signal/crypto/CMakeLists.txt
index 6359510..8f43895 100644
--- a/qomemo/signal/crypto/CMakeLists.txt
+++ b/qomemo/signal/crypto/CMakeLists.txt
@@ -7,4 +7,6 @@ target_sources(squawk PRIVATE
         hmac_sha256_openssl.h
         sha512_digest_openssl.cpp
         sha512_digest_openssl.h
+        ec.cpp
+        ec.h
         )
diff --git a/qomemo/signal/crypto/ec.cpp b/qomemo/signal/crypto/ec.cpp
new file mode 100644
index 0000000..875d2ee
--- /dev/null
+++ b/qomemo/signal/crypto/ec.cpp
@@ -0,0 +1,5 @@
+/*
+ * Created by victoria on 2021-06-17.
+*/
+
+#include "ec.h"
diff --git a/qomemo/signal/crypto/ec.h b/qomemo/signal/crypto/ec.h
new file mode 100644
index 0000000..280d00a
--- /dev/null
+++ b/qomemo/signal/crypto/ec.h
@@ -0,0 +1,20 @@
+/*
+ * Created by victoria on 2021-06-17.
+*/
+
+#pragma once
+
+#include <signal/signal_protocol.h>
+
+namespace Signal::Crypto {
+
+    class ECKeyPair {
+    public:
+        ECKeyPair();
+        ~ECKeyPair();
+
+    private:
+        ec_key_pair *ec;
+    };
+
+}
diff --git a/qomemo/signal/stores/identity_key_store.cpp b/qomemo/signal/stores/identity_key_store.cpp
index a8b28df..e80b7d3 100644
--- a/qomemo/signal/stores/identity_key_store.cpp
+++ b/qomemo/signal/stores/identity_key_store.cpp
@@ -4,13 +4,56 @@
 
 #include "identity_key_store.h"
 
-void Signal::Store::IdentityKeyStore::boundToContext(
-        signal_protocol_store_context *ctx) {
-    signal_protocol_identity_key_store store{};
+#include "qomemo/signal/util.h"
+#include <utility>
 
-    store.user_data = nullptr;
-    store.destroy_func = nullptr;
+Signal::Store::IdentityKeyStore::IdentityKeyStore(QXmpp::Omemo::DeviceService &deviceService, QString jid, int deviceId)
+        : jid(std::move(jid)), deviceId(deviceId), deviceService(deviceService) {
+    database = deviceService.getDatabase(jid);
+}
 
+int Signal::Store::IdentityKeyStore::getIdentityKeyPair(signal_buffer **public_data, signal_buffer **private_data) {
+    auto pk = database->loadIdentityKey(deviceId);
+    auto sk = database->loadIdentityKeySecret(deviceId);
+
+    *public_data = signal_buffer_create(reinterpret_cast<const uint8_t *>(pk->data()), pk->size());
+    *private_data = signal_buffer_create(reinterpret_cast<const uint8_t *>(sk->data()), sk->size());
+
+    return 0;
+}
+
+int Signal::Store::IdentityKeyStore::getLocalRegistrationId(uint32_t *registration_id) {
+    // TODO: Figure out what registration id is used for
+    *registration_id = 1;
+
+    return 0;
+}
+
+int Signal::Store::IdentityKeyStore::saveIdentity(const signal_protocol_address *address, uint8_t *key_data,
+                                                  size_t key_len) {
+    auto identityJid = Signal::Util::jidFromAddress(address);
+    auto identityKey = Signal::Util::byteArray(key_data, key_len);
+
+    deviceService.getDatabase(identityJid)->saveIdentityKey(address->device_id, identityKey);
+
+    return 0;
+}
+
+int Signal::Store::IdentityKeyStore::isTrustedIdentity(const signal_protocol_address *address, uint8_t *key_data,
+                                                       size_t key_len) {
+    auto identityJid = Signal::Util::jidFromAddress(address);
+    auto actualIdentityKey = deviceService.getDatabase(identityJid)->loadIdentityKey(address->device_id);
+
+    if (!actualIdentityKey.has_value()) {
+        return 1;
+    }
+
+    auto givenIdentityKey = Signal::Util::byteArray(key_data, key_len);
+
+    return givenIdentityKey == actualIdentityKey ? 1 : 0;
+}
+
+void Signal::Store::IdentityKeyStore::fillCallbacks(signal_protocol_identity_key_store &store) {
     store.get_identity_key_pair = [](signal_buffer **public_data, signal_buffer **private_data, void *ptr) {
         return static_cast<IdentityKeyStore *>(ptr)->getIdentityKeyPair(public_data, private_data);
     };
@@ -24,24 +67,4 @@ void Signal::Store::IdentityKeyStore::boundToContext(
     store.save_identity = [](const signal_protocol_address *address, uint8_t *key_data, size_t key_len, void *ptr) {
         return static_cast<IdentityKeyStore *>(ptr)->saveIdentity(address, key_data, key_len);
     };
-
-    signal_protocol_store_context_set_identity_key_store(ctx, &store);
-}
-
-int Signal::Store::IdentityKeyStore::getIdentityKeyPair(signal_buffer **public_data, signal_buffer **private_data) {
-    return 0;
-}
-
-int Signal::Store::IdentityKeyStore::getLocalRegistrationId(uint32_t *registration_id) {
-    return 0;
-}
-
-int Signal::Store::IdentityKeyStore::saveIdentity(const signal_protocol_address *address, uint8_t *key_data,
-                                                  size_t key_len) {
-    return 0;
-}
-
-int Signal::Store::IdentityKeyStore::isTrustedIdentity(const signal_protocol_address *address, uint8_t *key_data,
-                                                       size_t key_len) {
-    return 0;
 }
diff --git a/qomemo/signal/stores/identity_key_store.h b/qomemo/signal/stores/identity_key_store.h
index 4179430..017ca42 100644
--- a/qomemo/signal/stores/identity_key_store.h
+++ b/qomemo/signal/stores/identity_key_store.h
@@ -6,16 +6,27 @@
 
 #include <signal/signal_protocol.h>
 
+#include "qomemo/device_service.h"
+
 namespace Signal::Store {
 
     class IdentityKeyStore {
     public:
-        static void boundToContext(signal_protocol_store_context *ctx);
+        IdentityKeyStore(QXmpp::Omemo::DeviceService &deviceService, QString jid, int deviceId);
 
         int getIdentityKeyPair(signal_buffer **public_data, signal_buffer **private_data);
         int getLocalRegistrationId(uint32_t *registration_id);
         int saveIdentity(const signal_protocol_address *address, uint8_t *key_data, size_t key_len);
         int isTrustedIdentity(const signal_protocol_address *address, uint8_t *key_data, size_t key_len);
+
+        void fillCallbacks(signal_protocol_identity_key_store &store);
+
+        const QString jid;
+        const int deviceId;
+
+    private:
+        QXmpp::Omemo::DeviceService &deviceService;
+        QSharedPointer<QXmpp::Omemo::Database> database;
     };
 
 } // namespace Signal::Store
diff --git a/qomemo/signal/stores/pre_key_store.cpp b/qomemo/signal/stores/pre_key_store.cpp
index fe4cd60..72bfe72 100644
--- a/qomemo/signal/stores/pre_key_store.cpp
+++ b/qomemo/signal/stores/pre_key_store.cpp
@@ -4,29 +4,8 @@
 
 #include "pre_key_store.h"
 
-void Signal::Store::PreKeyStore::boundToContext(
-        signal_protocol_store_context *ctx) {
-    signal_protocol_pre_key_store store{};
-
-    store.destroy_func = nullptr;
-    store.user_data = nullptr;
-
-    store.contains_pre_key = [](uint32_t id, void *ptr) {
-        return static_cast<PreKeyStore *>(ptr)->containsPreKey(id);
-    };
-    store.load_pre_key = [](signal_buffer **record, uint32_t id, void *ptr) {
-        return static_cast<PreKeyStore *>(ptr)->loadPreKey(record, id);
-    };
-    store.remove_pre_key = [](uint32_t id, void *ptr) {
-        return static_cast<PreKeyStore *>(ptr)->removePreKey(id);
-    };
-    store.store_pre_key = [](uint32_t id, uint8_t *record, size_t size,
-                             void *ptr) {
-        return static_cast<PreKeyStore *>(ptr)->storePreKey(id, record, size);
-    };
-
-    signal_protocol_store_context_set_pre_key_store(ctx, &store);
-}
+Signal::Store::PreKeyStore::PreKeyStore(QSharedPointer<QXmpp::Omemo::Database> database) : database(
+        std::move(database)) {}
 
 int Signal::Store::PreKeyStore::containsPreKey(uint32_t pre_key_id) {
     return 0;
@@ -41,3 +20,19 @@ int Signal::Store::PreKeyStore::storePreKey(uint32_t pre_key_id, uint8_t *record
 }
 
 int Signal::Store::PreKeyStore::removePreKey(uint32_t pre_key_id) { return 0; }
+
+void Signal::Store::PreKeyStore::fillCallbacks(signal_protocol_pre_key_store &store) {
+    store.contains_pre_key = [](uint32_t id, void *ptr) {
+        return static_cast<PreKeyStore *>(ptr)->containsPreKey(id);
+    };
+    store.load_pre_key = [](signal_buffer **record, uint32_t id, void *ptr) {
+        return static_cast<PreKeyStore *>(ptr)->loadPreKey(record, id);
+    };
+    store.remove_pre_key = [](uint32_t id, void *ptr) {
+        return static_cast<PreKeyStore *>(ptr)->removePreKey(id);
+    };
+    store.store_pre_key = [](uint32_t id, uint8_t *record, size_t size,
+                             void *ptr) {
+        return static_cast<PreKeyStore *>(ptr)->storePreKey(id, record, size);
+    };
+}
diff --git a/qomemo/signal/stores/pre_key_store.h b/qomemo/signal/stores/pre_key_store.h
index eb70786..0ec1a79 100644
--- a/qomemo/signal/stores/pre_key_store.h
+++ b/qomemo/signal/stores/pre_key_store.h
@@ -6,16 +6,23 @@
 
 #include <signal/signal_protocol.h>
 
+#include "qomemo/device_service.h"
+
 namespace Signal::Store {
 
     class PreKeyStore {
     public:
-        static void boundToContext(signal_protocol_store_context *ctx);
+        explicit PreKeyStore(QSharedPointer<QXmpp::Omemo::Database> database);
 
         int containsPreKey(uint32_t pre_key_id);
         int loadPreKey(signal_buffer **record, uint32_t pre_key_id);
         int storePreKey(uint32_t pre_key_id, uint8_t *record, size_t record_len);
         int removePreKey(uint32_t pre_key_id);
+
+        void fillCallbacks(signal_protocol_pre_key_store &store);
+
+    private:
+        QSharedPointer<QXmpp::Omemo::Database> database;
     };
 
 } // namespace Signal::Store
diff --git a/qomemo/signal/util.cpp b/qomemo/signal/util.cpp
new file mode 100644
index 0000000..9c2ed08
--- /dev/null
+++ b/qomemo/signal/util.cpp
@@ -0,0 +1,14 @@
+/*
+ * Created by victoria on 2021-05-15.
+*/
+
+#include "util.h"
+
+QString Signal::Util::jidFromAddress(const signal_protocol_address *address) {
+    // TODO: Validate this
+    return QString::fromRawData(reinterpret_cast<const QChar *>(address->name), static_cast<int>(address->name_len));
+}
+
+QByteArray Signal::Util::byteArray(const uint8_t *data, size_t len) {
+    return QByteArray::fromRawData(reinterpret_cast<const char *>(data), static_cast<int>(len));
+}
diff --git a/qomemo/signal/util.h b/qomemo/signal/util.h
new file mode 100644
index 0000000..e1145ef
--- /dev/null
+++ b/qomemo/signal/util.h
@@ -0,0 +1,18 @@
+/*
+ * Created by victoria on 2021-05-15.
+*/
+
+#pragma once
+
+#include <signal/signal_protocol.h>
+
+#include <QString>
+#include <QByteArray>
+
+namespace Signal::Util {
+
+    QString jidFromAddress(const signal_protocol_address *address);
+
+    QByteArray byteArray(const uint8_t *data, size_t len);
+
+}
diff --git a/qomemo/variant/conversations.cpp b/qomemo/variant/conversations.cpp
index 4fc6af6..84926cb 100644
--- a/qomemo/variant/conversations.cpp
+++ b/qomemo/variant/conversations.cpp
@@ -4,10 +4,13 @@
 
 #include "conversations.h"
 
+#include "qomemo/bundle.h"
 #include "qomemo/device.h"
 #include "shared/qxmppfactories.h"
 
+#include <QDebug>
 #include <QXmppIq.h>
+#include <QXmppPubSubIq.h>
 
 using namespace QXmpp::Omemo;
 using namespace QXmpp::Factories;
@@ -41,13 +44,21 @@ Variant::Conversations::deviceListToXml(const DeviceList &deviceList) {
     return element;
 }
 
-DeviceList Variant::Conversations::deviceListFromXml(const QXmppElement &xml) {
+std::optional<DeviceList> Variant::Conversations::latestDeviceListFromPubSubNode(const QXmppElement &items) {
+    auto item = items.firstChildElement("item");
+    if (item.isNull())
+        return std::nullopt;
+
+    auto list = item.firstChildElement("list");
+    if (list.isNull())
+        return std::nullopt;
+
+    if (!elementMatches(list, "list", "eu.siacs.conversations.axolotl"))
+        return std::nullopt;
+
     DeviceList result{};
 
-    if (!elementMatches(xml, "list", "eu.siacs.conversations.axolotl"))
-        return result;
-
-    auto deviceElement = xml.firstChildElement("device");
+    auto deviceElement = list.firstChildElement("device");
     while (!deviceElement.isNull()) {
         result.devices.push_back(deviceFromXml(deviceElement));
         deviceElement = deviceElement.nextSiblingElement("device");
@@ -65,7 +76,34 @@ QXmppIq Variant::Conversations::deviceListSetIq(const DeviceList &deviceList) {
     item.appendChild(deviceListToXml(deviceList));
 
     auto publish = createElement("publish");
-    publish.setAttribute("node", "eu.siacs.conversations.axolotl.devicelist");
+    publish.setAttribute("node", getDeviceListNode());
+    publish.appendChild(item);
+
+    auto pubSub = createElement("pubsub", "http://jabber.org/protocol/pubsub");
+    pubSub.appendChild(publish);
+    pubSub.appendChild(createOpenPublishOptions());
+
+    iq.setExtensions({ pubSub });
+
+    return iq;
+}
+
+QString Variant::Conversations::getDeviceListNode() const {
+    return "eu.siacs.conversations.axolotl.devicelist";
+}
+
+QXmppIq Variant::Conversations::bundleSetIq(int deviceId, const Bundle &bundle) {
+    QXmppIq iq{};
+
+    iq.setType(QXmppIq::Set);
+
+    auto item = createElement("item");
+    item.appendChild(bundleToXml(bundle));
+
+    auto publish = createElement("publish");
+    publish.setAttribute(
+            "node",
+            QString("eu.siacs.conversations.axolotl.bundles:%s").arg(deviceId));
     publish.appendChild(item);
 
     auto pubSub = createElement("pubsub", "http://jabber.org/protocol/pubsub");
@@ -76,3 +114,92 @@ QXmppIq Variant::Conversations::deviceListSetIq(const DeviceList &deviceList) {
 
     return iq;
 }
+
+QXmppElement Variant::Conversations::bundleToXml(const Bundle &bundle) {
+    auto spkNode = createElement("signedPreKeyPublic");
+    spkNode.setAttribute("signedPreKeyId", QString::number(bundle.spkId));
+    spkNode.setValue(bundle.spk.toBase64());
+
+    auto spksNode = createElement("signedPreKeySignature");
+    spksNode.setValue(bundle.spks.toBase64());
+
+    auto ikNode = createElement("identityKey");
+    ikNode.setValue(bundle.ik.toBase64());
+
+    auto prekeysNode = createElement("prekeys");
+    for (const auto &pk : bundle.prekeys) {
+        prekeysNode.appendChild(preKeyToXml(pk));
+    }
+
+    auto result = createElement("bundle", "eu.siacs.conversations.axolotl");
+    result.appendChild(spkNode);
+    result.appendChild(spksNode);
+    result.appendChild(ikNode);
+    result.appendChild(prekeysNode);
+
+    return result;
+}
+
+QXmppElement Variant::Conversations::preKeyToXml(const PreKey &pk) {
+    auto x = createElement("preKeyPublic");
+    x.setAttribute("preKeyId", QString::number(pk.id));
+    x.setValue(pk.data.toBase64());
+
+    return x;
+}
+
+std::optional<PreKey> Variant::Conversations::preKeyFromXml(const QXmppElement &xml) {
+    if (!elementMatches(xml, "preKeyPublic"))
+        return std::nullopt;
+
+    auto pk = PreKey();
+    pk.id = xml.attribute("preKeyId").toInt();
+    pk.data = QByteArray::fromBase64Encoding(xml.value().toUtf8()).decoded;
+
+    return pk;
+}
+
+std::optional<Bundle> Variant::Conversations::bundleFromXml(const QXmppElement &xml) {
+    if (!elementMatches(xml, "bundle", "eu.siacs.conversations.axolotl"))
+        return std::nullopt;
+
+    auto spkNode = xml.firstChildElement("signedPreKeyPublic");
+    if (spkNode.isNull()) {
+        qWarning() << "'bundle': missing 'signedPreKeyPublic'";
+        return std::nullopt;
+    }
+
+    Bundle result{};
+
+    result.spk = QByteArray::fromBase64Encoding(spkNode.value().toUtf8()).decoded;
+    result.spkId = spkNode.attribute("signedPreKeyId").toInt();
+
+    auto spksNode = xml.firstChildElement("signedPreKeySignature");
+    if (spksNode.isNull()) {
+        qWarning() << "'bundle': missing 'signedPreKeySignature'";
+        return std::nullopt;
+    }
+
+    result.spks = QByteArray::fromBase64Encoding(spksNode.value().toUtf8()).decoded;
+
+    auto ikNode = xml.firstChildElement("identityKey");
+    if (ikNode.isNull()) {
+        qWarning() << "'bundle': missing 'identityKey'";
+        return std::nullopt;
+    }
+
+    result.ik = QByteArray::fromBase64Encoding(ikNode.value().toUtf8()).decoded;
+
+    auto prekeysNode = xml.firstChildElement("prekeys");
+    auto pkNode = prekeysNode.firstChildElement("preKeyPublic");
+
+    result.prekeys.clear();
+    while (!pkNode.isNull()) {
+        auto maybePk = preKeyFromXml(pkNode);
+        if (maybePk)
+            result.prekeys.push_back(*maybePk);
+        pkNode = pkNode.nextSiblingElement("preKeyPublic");
+    }
+
+    return result;
+}
diff --git a/qomemo/variant/conversations.h b/qomemo/variant/conversations.h
index 923e29b..5a467f0 100644
--- a/qomemo/variant/conversations.h
+++ b/qomemo/variant/conversations.h
@@ -15,9 +15,18 @@ namespace QXmpp::Omemo::Variant {
         QXmppElement deviceToXml(const Device &device) override;
         Device deviceFromXml(const QXmppElement &xml) override;
 
+        [[nodiscard]] QString getDeviceListNode() const override;
         QXmppElement deviceListToXml(const DeviceList &deviceList) override;
-        DeviceList deviceListFromXml(const QXmppElement &xml) override;
+        std::optional<DeviceList> latestDeviceListFromPubSubNode(const QXmppElement &items) override;
         QXmppIq deviceListSetIq(const DeviceList &deviceList) override;
+
+        QXmppElement preKeyToXml(const PreKey &pk) override;
+        std::optional<PreKey> preKeyFromXml(const QXmppElement &xml) override;
+
+        QXmppElement bundleToXml(const Bundle &bundle) override;
+        std::optional<Bundle> bundleFromXml(const QXmppElement &xml) override;
+
+        QXmppIq bundleSetIq(int deviceId, const Bundle &bundle) override;
     };
 
 } // namespace QXmpp::Omemo::Variant
diff --git a/qomemo/variant/omemo_base.h b/qomemo/variant/omemo_base.h
index 58910fe..88d2a9f 100644
--- a/qomemo/variant/omemo_base.h
+++ b/qomemo/variant/omemo_base.h
@@ -4,14 +4,17 @@
 
 #pragma once
 
-class QXmppElement;
+#include <optional>
 
+class QString;
+class QXmppElement;
 class QXmppIq;
 
 namespace QXmpp::Omemo {
 
+    class PreKey;
+    class Bundle;
     class Device;
-
     class DeviceList;
 
     namespace Variant {
@@ -23,9 +26,18 @@ namespace QXmpp::Omemo {
             virtual QXmppElement deviceToXml(const Device &device) = 0;
             virtual Device deviceFromXml(const QXmppElement &xml) = 0;
 
+            [[nodiscard]] virtual QString getDeviceListNode() const = 0;
             virtual QXmppElement deviceListToXml(const DeviceList &deviceList) = 0;
-            virtual DeviceList deviceListFromXml(const QXmppElement &xml) = 0;
+            virtual std::optional<DeviceList> latestDeviceListFromPubSubNode(const QXmppElement &xml) = 0;
             virtual QXmppIq deviceListSetIq(const DeviceList &deviceList) = 0;
+
+            virtual QXmppElement preKeyToXml(const PreKey &pk) = 0;
+            virtual std::optional<PreKey> preKeyFromXml(const QXmppElement &xml) = 0;
+
+            virtual QXmppElement bundleToXml(const Bundle& bundle) = 0;
+            virtual std::optional<Bundle> bundleFromXml(const QXmppElement& xml) = 0;
+
+            virtual QXmppIq bundleSetIq(int deviceId, const Bundle& bundle) = 0;
         };
 
     } // namespace Variant
diff --git a/ui/omemo/contactsettings.cpp b/ui/omemo/contactsettings.cpp
index c4d20b9..ba9704f 100644
--- a/ui/omemo/contactsettings.cpp
+++ b/ui/omemo/contactsettings.cpp
@@ -6,17 +6,19 @@
 #include "ui_contactsettings.h"
 #include "omemodevices.h"
 
-ContactSettings::ContactSettings(QWidget *parent)
-        : QDialog(parent), m_ui(new Ui::ContactSettings()) {
+ContactSettings::ContactSettings(QString jid, QWidget *parent)
+        : QDialog(parent), jid(std::move(jid)), m_ui(new Ui::ContactSettings()) {
     m_ui->setupUi(this);
 
     connect(m_ui->devicesButton, &QPushButton::clicked, this, &ContactSettings::openDeviceList);
+
+    setWindowTitle(QString("Encryption settings for %1").arg(this->jid));
 }
 
 ContactSettings::~ContactSettings() {}
 
 void ContactSettings::openDeviceList() {
-    auto devices = new OMEMODevices(this);
+    auto devices = new OMEMODevices(jid, this);
 
     devices->setAttribute(Qt::WA_DeleteOnClose);
     devices->show();
diff --git a/ui/omemo/contactsettings.h b/ui/omemo/contactsettings.h
index 4720c1f..37dc4c1 100644
--- a/ui/omemo/contactsettings.h
+++ b/ui/omemo/contactsettings.h
@@ -13,9 +13,11 @@ namespace Ui {
 class ContactSettings : public QDialog {
     Q_OBJECT
 public:
-    explicit ContactSettings(QWidget *parent = nullptr);
+    explicit ContactSettings(QString jid, QWidget *parent = nullptr);
     ~ContactSettings() override;
 
+    const QString jid;
+
 private slots:
     void openDeviceList();
 
diff --git a/ui/omemo/omemodevices.cpp b/ui/omemo/omemodevices.cpp
index 877ff27..5b08565 100644
--- a/ui/omemo/omemodevices.cpp
+++ b/ui/omemo/omemodevices.cpp
@@ -5,9 +5,11 @@
 #include "omemodevices.h"
 #include "ui_omemodevices.h"
 
-OMEMODevices::OMEMODevices(QWidget *parent)
-        : QDialog(parent), m_ui(new Ui::OMEMODevices()) {
+OMEMODevices::OMEMODevices(QString jid, QWidget *parent)
+        : QDialog(parent), jid(std::move(jid)), m_ui(new Ui::OMEMODevices()) {
     m_ui->setupUi(this);
+
+    setWindowTitle(QString("%1's OMEMO devices").arg(this->jid));
 }
 
 OMEMODevices::~OMEMODevices() {}
diff --git a/ui/omemo/omemodevices.h b/ui/omemo/omemodevices.h
index 70dbb0f..529711e 100644
--- a/ui/omemo/omemodevices.h
+++ b/ui/omemo/omemodevices.h
@@ -13,9 +13,11 @@ namespace Ui {
 class OMEMODevices : public QDialog {
     Q_OBJECT
 public:
-    explicit OMEMODevices(QWidget *parent = nullptr);
+    explicit OMEMODevices(QString jid, QWidget *parent = nullptr);
     ~OMEMODevices() override;
 
+    const QString jid;
+
 private:
     QScopedPointer<Ui::OMEMODevices> m_ui;
 };
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 13c452a..1cd9929 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -20,6 +20,7 @@
 #include "ui_squawk.h"
 #include <QDebug>
 #include <QIcon>
+#include <utility>
 #include <ui/omemo/omemodevices.h>
 
 Squawk::Squawk(QWidget *parent) :
@@ -61,7 +62,6 @@ Squawk::Squawk(QWidget *parent) :
     connect(m_ui->roster, &QTreeView::collapsed, this, &Squawk::onItemCollepsed);
     connect(m_ui->roster->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &Squawk::onRosterSelectionChanged);
     connect(&rosterModel, &Models::Roster::unnoticedMessage, this, &Squawk::onUnnoticedMessage);
-    connect(m_ui->actionDeviceList, &QAction::triggered, this, &Squawk::onOMEMODevices);
 
     connect(rosterModel.accountsModel, &Models::Accounts::sizeChanged, this, &Squawk::onAccountsSizeChanged);
     connect(&rosterModel, &Models::Roster::requestArchive, this, &Squawk::onRequestArchive);
@@ -136,8 +136,8 @@ void Squawk::onNewContact()
     nc->exec();
 }
 
-void Squawk::onOMEMODevices() {
-  auto od = new OMEMODevices(this);
+void Squawk::openDeviceList(QString bareJid) {
+  auto od = new OMEMODevices(std::move(bareJid), this);
 
   od->setAttribute(Qt::WA_DeleteOnClose);
   od->show();
@@ -539,6 +539,10 @@ void Squawk::onRosterContextMenu(const QPoint& point)
                         emit connectAccount(name);
                     });
                 }
+
+                QAction* devices = contextMenu->addAction(Shared::icon("security-high"), tr("Devices"));
+                devices->setEnabled(active);
+                connect(devices, &QAction::triggered, [this, acc]() { openDeviceList(acc->getBareJid()); });
                 
                 QAction* card = contextMenu->addAction(Shared::icon("user-properties"), tr("VCard"));
                 card->setEnabled(active);
diff --git a/ui/squawk.h b/ui/squawk.h
index a4c130e..a7318de 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -155,7 +155,7 @@ private slots:
     void onPasswordPromptRejected();
     void onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous);
     void onContextAboutToHide();
-    void onOMEMODevices();
+    void openDeviceList(QString bareJid);
     
     void onUnnoticedMessage(const QString& account, const Shared::Message& msg);
     
diff --git a/ui/squawk.ui b/ui/squawk.ui
index cb48de6..a029a44 100644
--- a/ui/squawk.ui
+++ b/ui/squawk.ui
@@ -169,17 +169,7 @@
     <property name="title">
      <string>Settings</string>
     </property>
-    <widget class="QMenu" name="menuOMEMO">
-     <property name="title">
-      <string>OMEMO</string>
-     </property>
-     <property name="icon">
-      <iconset theme="security-high"/>
-     </property>
-     <addaction name="actionDeviceList"/>
-    </widget>
     <addaction name="actionAccounts"/>
-    <addaction name="menuOMEMO"/>
    </widget>
    <widget class="QMenu" name="menuFile">
     <property name="title">
@@ -236,7 +226,8 @@
   </action>
   <action name="actionDeviceList">
    <property name="icon">
-    <iconset theme="computer-laptop"/>
+    <iconset theme="computer-laptop">
+     <normaloff>.</normaloff>.</iconset>
    </property>
    <property name="text">
     <string>Device list</string>
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index 2b2547b..518847f 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -437,7 +437,7 @@ void Conversation::onFeedContext(const QPoint& pos)
 }
 
 void Conversation::openEncryptionSettings() {
-    auto cs = new ContactSettings(this);
+    auto cs = new ContactSettings(palJid, this);
 
     cs->setAttribute(Qt::WA_DeleteOnClose);
     cs->show();

From 6c9f1ab964776bf4f26a5fd9761f08a3faa06701 Mon Sep 17 00:00:00 2001
From: vae <vae@programming.socks.town>
Date: Thu, 22 Jul 2021 23:01:30 +0300
Subject: [PATCH 20/20] feat: wip omemo + key publish

---
 qomemo/qxmpp_omemo_manager.cpp                | 40 +++++++++++--
 qomemo/qxmpp_omemo_manager.h                  |  5 +-
 qomemo/signal/context.cpp                     | 10 +++-
 qomemo/signal/context.h                       |  1 +
 qomemo/signal/stores/sender_key_store.cpp     | 34 +++++------
 qomemo/signal/stores/sender_key_store.h       |  4 +-
 qomemo/signal/stores/session_store.cpp        | 58 +++++++++----------
 qomemo/signal/stores/session_store.h          |  4 +-
 qomemo/signal/stores/signed_pre_key_store.cpp | 48 +++++++--------
 qomemo/signal/stores/signed_pre_key_store.h   |  3 +-
 qomemo/signal/stores/store_context.cpp        | 14 ++++-
 qomemo/signal/stores/store_context.h          | 18 ++++++
 qomemo/variant/conversations.cpp              |  4 +-
 13 files changed, 147 insertions(+), 96 deletions(-)

diff --git a/qomemo/qxmpp_omemo_manager.cpp b/qomemo/qxmpp_omemo_manager.cpp
index 788fc96..e70754c 100644
--- a/qomemo/qxmpp_omemo_manager.cpp
+++ b/qomemo/qxmpp_omemo_manager.cpp
@@ -22,7 +22,8 @@ using namespace QXmpp::Omemo;
 
 Manager::Manager()
         : deviceService{new DeviceService(this)},
-          omemoVariant(new Variant::Conversations) {
+          omemoVariant(new Variant::Conversations),
+          signalContext(new Signal::Context) {
     connect(this, &Manager::deviceListReceived, deviceService.get(), &DeviceService::onDeviceListReceived);
     connect(this, &Manager::deviceListNotFound, deviceService.get(), &DeviceService::onDeviceListNotFound);
     connect(this, &Manager::deviceListNotFound, this, [this](const QString &jid) {
@@ -42,7 +43,7 @@ bool QXmpp::Omemo::Manager::handleStanza(const QDomElement &stanza) {
 
     std::cout << str.toStdString();
 
-    if (handleDeviceList(stanza) || handleMissingDeviceList(stanza))
+    if (handleDeviceList(stanza) || handleMissingDeviceList(stanza) || handleEncryptedMessage(stanza))
         return true;
 
     return false;
@@ -98,6 +99,19 @@ bool Manager::handleMissingDeviceList(const QDomElement &stanza) {
     return true;
 }
 
+bool Manager::handleEncryptedMessage(const QDomElement &stanza) {
+    if (stanza.tagName() != "message")
+        return false;
+
+    auto encrypted = stanza.firstChildElement("encrypted");
+    if (encrypted.isNull())
+        return false;
+
+    qDebug() << "!!!! Got encrypted message!!";
+
+    return true;
+}
+
 void QXmpp::Omemo::Manager::setClient(QXmppClient *client) {
     QXmppClientExtension::setClient(client);
 
@@ -178,7 +192,7 @@ void Manager::generateDeviceForSelfIfNeeded(const DeviceList &currentList) {
 
     qInfo() << "Generating device";
 
-    auto deviceId = QRandomGenerator64{}.bounded(INT32_MAX);
+    auto deviceId = QRandomGenerator64::system()->bounded(INT32_MAX);
     db->saveActiveDeviceId(deviceId);
 
     Device device{};
@@ -186,11 +200,11 @@ void Manager::generateDeviceForSelfIfNeeded(const DeviceList &currentList) {
 
     auto updatedList = currentList;
 
-    auto bundle = Bundle();
 
     updatedList.devices.push_back(device);
-
     publishDeviceList(updatedList);
+
+    auto bundle = generateAndSaveBundle(deviceId);
     publishBundle(deviceId, bundle);
 }
 
@@ -213,6 +227,8 @@ Bundle Manager::generateAndSaveBundle(int deviceId) {
     database->saveIdentityKey(deviceId, identityKey);
     database->saveIdentityKeySecret(deviceId, identityKeySecret);
 
+    result.ik = identityKey;
+
     // Generate SPK
     ec_key_pair *ecSpk;
     curve_generate_key_pair(signalContext->temporaryGetContextUnsafeForRawAccessThatNeedsToBeWrapped(), &ecSpk);
@@ -227,11 +243,15 @@ Bundle Manager::generateAndSaveBundle(int deviceId) {
                               signal_buffer_len(spkPublic));
 
     SignedPreKey spk{};
-    spk.id = 0;
+    spk.id = 1;
     spk.key.publicKey = QByteArray((const char *) signal_buffer_const_data(spkPublic), (int) signal_buffer_len(spkPublic));
     spk.key.secretKey = QByteArray((const char *) signal_buffer_const_data(spkSecret), (int) signal_buffer_len(spkSecret));
     spk.signature = QByteArray((const char *) signal_buffer_const_data(signature), (int) signal_buffer_len(signature));
 
+    result.spk = spk.key.publicKey;
+    result.spks = spk.signature;
+    result.spkId = 1;
+
     // Generate 100 PK
     for (auto i = 1; i <= 100; ++i) {
         ec_key_pair *currentPreKey;
@@ -247,6 +267,12 @@ Bundle Manager::generateAndSaveBundle(int deviceId) {
 
         database->savePreKey(deviceId, i, preKey);
 
+        PreKey pk{};
+        pk.data = preKey.publicKey;
+        pk.id = i;
+
+        result.prekeys.append(pk);
+
         signal_buffer_free(pkPublic);
         signal_buffer_free(pkSecret);
         SIGNAL_UNREF(currentPreKey);
@@ -261,4 +287,6 @@ Bundle Manager::generateAndSaveBundle(int deviceId) {
     signal_buffer_free(spkPublic);
     signal_buffer_free(spkSecret);
     SIGNAL_UNREF(ecSpk);
+
+    return result;
 }
diff --git a/qomemo/qxmpp_omemo_manager.h b/qomemo/qxmpp_omemo_manager.h
index 7f04026..03c2aa2 100644
--- a/qomemo/qxmpp_omemo_manager.h
+++ b/qomemo/qxmpp_omemo_manager.h
@@ -8,6 +8,7 @@
 #include "qomemo.h"
 #include "variant/omemo_base.h"
 
+#include <memory>
 #include <QXmppClientExtension.h>
 
 namespace Signal {
@@ -29,6 +30,8 @@ namespace QXmpp::Omemo {
 
         bool handleMissingDeviceList(const QDomElement& stanza);
 
+        bool handleEncryptedMessage(const QDomElement& stanza);
+
         QSharedPointer<DeviceService> getDeviceService();
 
         Bundle generateAndSaveBundle(int deviceId);
@@ -55,7 +58,7 @@ namespace QXmpp::Omemo {
     private:
         QSharedPointer<DeviceService> deviceService;
         QScopedPointer<Variant::Base> omemoVariant;
-        QScopedPointer<Signal::Context> signalContext;
+        std::shared_ptr<Signal::Context> signalContext;
     };
 
 } // namespace QXmpp::Omemo
diff --git a/qomemo/signal/context.cpp b/qomemo/signal/context.cpp
index 6253faa..75d3557 100644
--- a/qomemo/signal/context.cpp
+++ b/qomemo/signal/context.cpp
@@ -3,12 +3,18 @@
  */
 
 #include "context.h"
+#include "crypto/crypto.h"
 
 using namespace Signal;
 
-Context::Context() {}
+Context::Context() : cryptoProvider{ Signal::Crypto::createProvider() } {
+    signal_context_create(&ctx, nullptr);
+    signal_context_set_crypto_provider(ctx, &cryptoProvider);
+}
 
-Context::~Context() {}
+Context::~Context() {
+    signal_context_destroy(ctx);
+}
 
 std::unique_ptr<Crypto::ECKeyPair> Context::generateCurveKeyPair() {
     auto result = std::unique_ptr<Crypto::ECKeyPair>();
diff --git a/qomemo/signal/context.h b/qomemo/signal/context.h
index b829aca..30941d9 100644
--- a/qomemo/signal/context.h
+++ b/qomemo/signal/context.h
@@ -25,6 +25,7 @@ namespace Signal {
         signal_context *temporaryGetContextUnsafeForRawAccessThatNeedsToBeWrapped();
 
     private:
+        signal_crypto_provider cryptoProvider{};
         signal_context *ctx{nullptr};
     };
 
diff --git a/qomemo/signal/stores/sender_key_store.cpp b/qomemo/signal/stores/sender_key_store.cpp
index a1cb687..2db5231 100644
--- a/qomemo/signal/stores/sender_key_store.cpp
+++ b/qomemo/signal/stores/sender_key_store.cpp
@@ -4,26 +4,6 @@
 
 #include "sender_key_store.h"
 
-void Signal::Store::SenderKeyStore::boundToContext(
-        signal_protocol_store_context *ctx) {
-    signal_protocol_sender_key_store store{};
-
-    store.user_data = nullptr;
-    store.destroy_func = nullptr;
-
-    store.load_sender_key = [](signal_buffer **record, signal_buffer **user_record,
-                               const signal_protocol_sender_key_name *sender_key_name, void *ptr) {
-        return static_cast<SenderKeyStore *>(ptr)->loadSenderKey(record, user_record, sender_key_name);
-    };
-    store.store_sender_key = [](const signal_protocol_sender_key_name *sender_key_name, uint8_t *record,
-                                size_t record_len, uint8_t *user_record, size_t user_record_len, void *ptr) {
-        return static_cast<SenderKeyStore *>(ptr)->storeSenderKey(sender_key_name, record, record_len, user_record,
-                                                                  user_record_len);
-    };
-
-    signal_protocol_store_context_set_sender_key_store(ctx, &store);
-}
-
 int Signal::Store::SenderKeyStore::loadSenderKey(signal_buffer **record, signal_buffer **user_record,
                                                  const signal_protocol_sender_key_name *sender_key_name) {
     return 0;
@@ -34,3 +14,17 @@ int Signal::Store::SenderKeyStore::storeSenderKey(const signal_protocol_sender_k
                                                   size_t user_record_len) {
     return 0;
 }
+
+void Signal::Store::SenderKeyStore::fillCallbacks(signal_protocol_sender_key_store &store) {
+    store.user_data = nullptr;
+    store.destroy_func = nullptr;
+    store.load_sender_key = [](signal_buffer **record, signal_buffer **user_record,
+                               const signal_protocol_sender_key_name *sender_key_name, void *ptr) {
+        return static_cast<SenderKeyStore *>(ptr)->loadSenderKey(record, user_record, sender_key_name);
+    };
+    store.store_sender_key = [](const signal_protocol_sender_key_name *sender_key_name, uint8_t *record,
+                                size_t record_len, uint8_t *user_record, size_t user_record_len, void *ptr) {
+        return static_cast<SenderKeyStore *>(ptr)->storeSenderKey(sender_key_name, record, record_len, user_record,
+                                                                  user_record_len);
+    };
+}
diff --git a/qomemo/signal/stores/sender_key_store.h b/qomemo/signal/stores/sender_key_store.h
index 904377e..a5abcc6 100644
--- a/qomemo/signal/stores/sender_key_store.h
+++ b/qomemo/signal/stores/sender_key_store.h
@@ -10,12 +10,12 @@ namespace Signal::Store {
 
     class SenderKeyStore {
     public:
-        static void boundToContext(signal_protocol_store_context *ctx);
-
         int storeSenderKey(const signal_protocol_sender_key_name *sender_key_name, uint8_t *record, size_t record_len,
                            uint8_t *user_record, size_t user_record_len);
         int loadSenderKey(signal_buffer **record, signal_buffer **user_record,
                           const signal_protocol_sender_key_name *sender_key_name);
+
+        void fillCallbacks(signal_protocol_sender_key_store &store);
     };
 
 } // namespace Signal::Store
diff --git a/qomemo/signal/stores/session_store.cpp b/qomemo/signal/stores/session_store.cpp
index 311b3e6..4b9a5f9 100644
--- a/qomemo/signal/stores/session_store.cpp
+++ b/qomemo/signal/stores/session_store.cpp
@@ -4,38 +4,6 @@
 
 #include "session_store.h"
 
-void Signal::Store::SessionStore::boundToContext(
-        signal_protocol_store_context *ctx) {
-    signal_protocol_session_store store{};
-
-    store.user_data = nullptr;
-    store.destroy_func = nullptr;
-
-    store.load_session_func = [](signal_buffer **record, signal_buffer **user_record,
-                                 const signal_protocol_address *address, void *ptr) {
-        return static_cast<SessionStore *>(ptr)->loadSession(record, user_record, address);
-    };
-    store.get_sub_device_sessions_func = [](signal_int_list **sessions, const char *name, size_t name_len, void *ptr) {
-        return static_cast<SessionStore *>(ptr)->getSubDeviceSessions(sessions, name, name_len);
-    };
-    store.store_session_func = [](const signal_protocol_address *address, uint8_t *record, size_t record_len,
-                                  uint8_t *user_record, size_t user_record_len, void *ptr) {
-        return static_cast<SessionStore *>(ptr)->storeSession(address, record, record_len, user_record,
-                                                              user_record_len);
-    };
-    store.contains_session_func = [](const signal_protocol_address *address, void *ptr) {
-        return static_cast<SessionStore *>(ptr)->containsSession(address);
-    };
-    store.delete_session_func = [](const signal_protocol_address *address, void *ptr) {
-        return static_cast<SessionStore *>(ptr)->deleteSession(address);
-    };
-    store.delete_all_sessions_func = [](const char *name, size_t name_len, void *ptr) {
-        return static_cast<SessionStore *>(ptr)->deleteAllSessions(name, name_len);
-    };
-
-    signal_protocol_store_context_set_session_store(ctx, &store);
-}
-
 int Signal::Store::SessionStore::loadSession(signal_buffer **record, signal_buffer **user_record,
                                              const signal_protocol_address *address) {
     return 0;
@@ -61,3 +29,29 @@ int Signal::Store::SessionStore::deleteSession(const signal_protocol_address *ad
 int Signal::Store::SessionStore::deleteAllSessions(const char *name, size_t name_len) {
     return 0;
 }
+
+void Signal::Store::SessionStore::fillCallbacks(signal_protocol_session_store &store) {
+    store.user_data = nullptr;
+    store.destroy_func = nullptr;
+    store.load_session_func = [](signal_buffer **record, signal_buffer **user_record,
+                                 const signal_protocol_address *address, void *ptr) {
+        return static_cast<SessionStore *>(ptr)->loadSession(record, user_record, address);
+    };
+    store.get_sub_device_sessions_func = [](signal_int_list **sessions, const char *name, size_t name_len, void *ptr) {
+        return static_cast<SessionStore *>(ptr)->getSubDeviceSessions(sessions, name, name_len);
+    };
+    store.store_session_func = [](const signal_protocol_address *address, uint8_t *record, size_t record_len,
+                                  uint8_t *user_record, size_t user_record_len, void *ptr) {
+        return static_cast<SessionStore *>(ptr)->storeSession(address, record, record_len, user_record,
+                                                              user_record_len);
+    };
+    store.contains_session_func = [](const signal_protocol_address *address, void *ptr) {
+        return static_cast<SessionStore *>(ptr)->containsSession(address);
+    };
+    store.delete_session_func = [](const signal_protocol_address *address, void *ptr) {
+        return static_cast<SessionStore *>(ptr)->deleteSession(address);
+    };
+    store.delete_all_sessions_func = [](const char *name, size_t name_len, void *ptr) {
+        return static_cast<SessionStore *>(ptr)->deleteAllSessions(name, name_len);
+    };
+}
diff --git a/qomemo/signal/stores/session_store.h b/qomemo/signal/stores/session_store.h
index 848e5b8..c773c83 100644
--- a/qomemo/signal/stores/session_store.h
+++ b/qomemo/signal/stores/session_store.h
@@ -10,8 +10,6 @@ namespace Signal::Store {
 
     class SessionStore {
     public:
-        static void boundToContext(signal_protocol_store_context *ctx);
-
         int loadSession(signal_buffer **record, signal_buffer **user_record, const signal_protocol_address *address);
         int getSubDeviceSessions(signal_int_list **sessions, const char *name, size_t name_len);
         int storeSession(const signal_protocol_address *address, uint8_t *record, size_t record_len,
@@ -19,6 +17,8 @@ namespace Signal::Store {
         int containsSession(const signal_protocol_address *address);
         int deleteSession(const signal_protocol_address *address);
         int deleteAllSessions(const char *name, size_t name_len);
+
+        void fillCallbacks(signal_protocol_session_store &store);
     };
 
 } // namespace Signal::Store
diff --git a/qomemo/signal/stores/signed_pre_key_store.cpp b/qomemo/signal/stores/signed_pre_key_store.cpp
index 43bae1e..483465d 100644
--- a/qomemo/signal/stores/signed_pre_key_store.cpp
+++ b/qomemo/signal/stores/signed_pre_key_store.cpp
@@ -4,33 +4,6 @@
 
 #include "signed_pre_key_store.h"
 
-void Signal::Store::SignedPreKeyStore::boundToContext(
-        signal_protocol_store_context *ctx) {
-    signal_protocol_signed_pre_key_store store{};
-
-    store.user_data = nullptr;
-    store.destroy_func = nullptr;
-
-    store.load_signed_pre_key = [](signal_buffer **record, uint32_t signed_pre_key_id, void *ptr) {
-        return static_cast<SignedPreKeyStore *>(ptr)->loadSignedPreKey(
-                record, signed_pre_key_id);
-    };
-    store.store_signed_pre_key = [](uint32_t signed_pre_key_id, uint8_t *record, size_t record_len, void *ptr) {
-        return static_cast<SignedPreKeyStore *>(ptr)->storeSignedPreKey(
-                signed_pre_key_id, record, record_len);
-    };
-    store.contains_signed_pre_key = [](uint32_t signed_pre_key_id, void *ptr) {
-        return static_cast<SignedPreKeyStore *>(ptr)->containsSignedPreKey(
-                signed_pre_key_id);
-    };
-    store.remove_signed_pre_key = [](uint32_t signed_pre_key_id, void *ptr) {
-        return static_cast<SignedPreKeyStore *>(ptr)->removeSignedPreKey(
-                signed_pre_key_id);
-    };
-
-    signal_protocol_store_context_set_signed_pre_key_store(ctx, &store);
-}
-
 int Signal::Store::SignedPreKeyStore::loadSignedPreKey(signal_buffer **record, uint32_t signed_pre_key_id) {
     return 0;
 }
@@ -47,3 +20,24 @@ int Signal::Store::SignedPreKeyStore::containsSignedPreKey(uint32_t signed_pre_k
 int Signal::Store::SignedPreKeyStore::removeSignedPreKey(uint32_t signed_pre_key_id) {
     return 0;
 }
+
+void Signal::Store::SignedPreKeyStore::fillCallbacks(signal_protocol_signed_pre_key_store &store) {
+    store.user_data = nullptr;
+    store.destroy_func = nullptr;
+    store.load_signed_pre_key = [](signal_buffer **record, uint32_t signed_pre_key_id, void *ptr) {
+        return static_cast<SignedPreKeyStore *>(ptr)->loadSignedPreKey(
+                record, signed_pre_key_id);
+    };
+    store.store_signed_pre_key = [](uint32_t signed_pre_key_id, uint8_t *record, size_t record_len, void *ptr) {
+        return static_cast<SignedPreKeyStore *>(ptr)->storeSignedPreKey(
+                signed_pre_key_id, record, record_len);
+    };
+    store.contains_signed_pre_key = [](uint32_t signed_pre_key_id, void *ptr) {
+        return static_cast<SignedPreKeyStore *>(ptr)->containsSignedPreKey(
+                signed_pre_key_id);
+    };
+    store.remove_signed_pre_key = [](uint32_t signed_pre_key_id, void *ptr) {
+        return static_cast<SignedPreKeyStore *>(ptr)->removeSignedPreKey(
+                signed_pre_key_id);
+    };
+}
diff --git a/qomemo/signal/stores/signed_pre_key_store.h b/qomemo/signal/stores/signed_pre_key_store.h
index 3f58851..2544741 100644
--- a/qomemo/signal/stores/signed_pre_key_store.h
+++ b/qomemo/signal/stores/signed_pre_key_store.h
@@ -10,11 +10,12 @@ namespace Signal::Store {
 
     class SignedPreKeyStore {
     public:
-        static void boundToContext(signal_protocol_store_context *ctx);
         int loadSignedPreKey(signal_buffer **record, uint32_t signed_pre_key_id);
         int storeSignedPreKey(uint32_t signed_pre_key_id, uint8_t *record, size_t record_len);
         int containsSignedPreKey(uint32_t signed_pre_key_id);
         int removeSignedPreKey(uint32_t signed_pre_key_id);
+
+        void fillCallbacks(signal_protocol_signed_pre_key_store &store);
     };
 
 } // namespace Signal::Store
diff --git a/qomemo/signal/stores/store_context.cpp b/qomemo/signal/stores/store_context.cpp
index dd5bb5e..e4dd58a 100644
--- a/qomemo/signal/stores/store_context.cpp
+++ b/qomemo/signal/stores/store_context.cpp
@@ -4,8 +4,20 @@
 
 #include "store_context.h"
 
-Signal::Store::Context::Context(signal_context *global) {
+Signal::Store::Context::Context(signal_context *global) : identityKeyStore(), preKeyStore(), senderKeyStore(), sessionStore(), signedPreKeyStore() {
     signal_protocol_store_context_create(&ctx, global);
+
+    identityKeyStore->fillCallbacks(iks);
+    preKeyStore->fillCallbacks(pks);
+    senderKeyStore->fillCallbacks(sks);
+    sessionStore->fillCallbacks(ss);
+    signedPreKeyStore->fillCallbacks(spks);
+
+    signal_protocol_store_context_set_identity_key_store(ctx, &iks);
+    signal_protocol_store_context_set_pre_key_store(ctx, &pks);
+    signal_protocol_store_context_set_sender_key_store(ctx, &sks);
+    signal_protocol_store_context_set_session_store(ctx, &ss);
+    signal_protocol_store_context_set_signed_pre_key_store(ctx, &spks);
 }
 
 Signal::Store::Context::~Context() {
diff --git a/qomemo/signal/stores/store_context.h b/qomemo/signal/stores/store_context.h
index 204d9b9..7c1ea55 100644
--- a/qomemo/signal/stores/store_context.h
+++ b/qomemo/signal/stores/store_context.h
@@ -6,6 +6,12 @@
 
 #include <signal/signal_protocol.h>
 
+#include "identity_key_store.h"
+#include "pre_key_store.h"
+#include "sender_key_store.h"
+#include "session_store.h"
+#include "signed_pre_key_store.h"
+
 namespace Signal::Store {
 
     class Context {
@@ -19,6 +25,18 @@ namespace Signal::Store {
 
     private:
         signal_protocol_store_context *ctx{nullptr};
+
+        signal_protocol_identity_key_store iks{};
+        signal_protocol_pre_key_store pks{};
+        signal_protocol_sender_key_store sks{};
+        signal_protocol_session_store ss{};
+        signal_protocol_signed_pre_key_store spks{};
+
+        std::unique_ptr<IdentityKeyStore> identityKeyStore;
+        std::unique_ptr<PreKeyStore> preKeyStore;
+        std::unique_ptr<SenderKeyStore> senderKeyStore;
+        std::unique_ptr<SessionStore> sessionStore;
+        std::unique_ptr<SignedPreKeyStore> signedPreKeyStore;
     };
 
 } // namespace Signal::Store
diff --git a/qomemo/variant/conversations.cpp b/qomemo/variant/conversations.cpp
index 84926cb..1658c37 100644
--- a/qomemo/variant/conversations.cpp
+++ b/qomemo/variant/conversations.cpp
@@ -103,14 +103,14 @@ QXmppIq Variant::Conversations::bundleSetIq(int deviceId, const Bundle &bundle)
     auto publish = createElement("publish");
     publish.setAttribute(
             "node",
-            QString("eu.siacs.conversations.axolotl.bundles:%s").arg(deviceId));
+            QString("eu.siacs.conversations.axolotl.bundles:%1").arg(deviceId));
     publish.appendChild(item);
 
     auto pubSub = createElement("pubsub", "http://jabber.org/protocol/pubsub");
     pubSub.appendChild(publish);
     pubSub.appendChild(createOpenPublishOptions());
 
-    iq.extensions().push_back(pubSub);
+    iq.setExtensions({ pubSub });
 
     return iq;
 }