Piac test code coverage report
Current view: top level - src - cli_matrix_thread.cpp (source / functions) Hit Total Coverage
Commit: Piac-DEBUG Lines: 240 532 45.1 %
Date: 2022-12-16 13:46:15 Functions: 28 156 17.9 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 369 1813 20.4 %

           Branch data     Line data    Source code
       1                 :            : // *****************************************************************************
       2                 :            : /*!
       3                 :            :   \file      src/cli_matrix_thread.cpp
       4                 :            :   \copyright 2022-2025 J. Bakosi,
       5                 :            :              All rights reserved. See the LICENSE file for details.
       6                 :            :   \brief     Piac matrix client functionality
       7                 :            : */
       8                 :            : // *****************************************************************************
       9                 :            : 
      10                 :            : #include <iostream>
      11                 :            : #include <fstream>
      12                 :            : 
      13                 :            : #include <nlohmann/json.hpp>
      14                 :            : 
      15                 :            : #if defined(__clang__)
      16                 :            :   #pragma clang diagnostic push
      17                 :            :   #pragma clang diagnostic ignored "-Wpadded"
      18                 :            :   #pragma clang diagnostic ignored "-Wextra-semi"
      19                 :            :   #pragma clang diagnostic ignored "-Wweak-vtables"
      20                 :            :   #pragma clang diagnostic ignored "-Wold-style-cast"
      21                 :            :   #pragma clang diagnostic ignored "-Wc++98-compat-pedantic"
      22                 :            :   #pragma clang diagnostic ignored "-Wdocumentation-unknown-command"
      23                 :            : #endif
      24                 :            : 
      25                 :            : #include "mtxclient/http/client.hpp"
      26                 :            : #include "mtxclient/crypto/client.hpp"
      27                 :            : #include "mtx/responses/sync.hpp"
      28                 :            : #include "mtx/responses/common.hpp"
      29                 :            : #include "mtx/responses/create_room.hpp"
      30                 :            : #include "mtx/events.hpp"
      31                 :            : #include "mtx/events/encrypted.hpp"
      32                 :            : 
      33                 :            : #if defined(__clang__)
      34                 :            :   #pragma clang diagnostic pop
      35                 :            : #endif
      36                 :            : 
      37                 :            : #include "cli_matrix_thread.hpp"
      38                 :            : #include "logging_util.hpp"
      39                 :            : #include "string_util.hpp"
      40                 :            : 
      41                 :            : constexpr auto OLM_ALGO = "m.olm.v1.curve25519-aes-sha2";
      42                 :            : static std::shared_ptr< mtx::http::Client> g_mtxclient = nullptr;
      43                 :            : static std::shared_ptr< mtx::crypto::OlmClient> g_olmclient = nullptr;
      44                 :            : static std::string g_mtx_account_db_filename;
      45                 :            : static std::string g_mtx_session_db_filename;
      46                 :            : static std::string g_mtx_db_storage_key;
      47                 :            : static std::vector< zmqpp::socket > g_msg_mtx;
      48                 :            : 
      49                 :            : uint16_t piac::g_matrix_sync_timeout = 10'000;     // milliseconds
      50                 :            : bool piac::g_matrix_connected = false;
      51                 :            : bool piac::g_matrix_shutdown = false;
      52                 :            : zmqpp::context piac::g_ctx_msg;
      53                 :            : 
      54                 :            : struct OutboundSessionData {
      55                 :            :   std::string session_id;
      56                 :            :   std::string session_key;
      57                 :            :   uint64_t message_index = 0;
      58                 :            : };
      59                 :            : 
      60                 :            : static inline void
      61                 :          0 : to_json( nlohmann::json& obj, const OutboundSessionData& msg ) 
      62                 :            : // *****************************************************************************
      63                 :            : //! Serialize OutboundSessionData to json format
      64                 :            : //! \param[in,out] obj nlohmann::json object to write to
      65                 :            : //! \param[in] msg OutboundSessionData message to read from
      66                 :            : // *****************************************************************************
      67                 :            : {
      68         [ -  - ]:          0 :   obj[ "session_id" ] = msg.session_id;
      69         [ -  - ]:          0 :   obj[ "session_key" ] = msg.session_key;
      70         [ -  - ]:          0 :   obj[ "message_index" ] = msg.message_index;
      71                 :          0 : }
      72                 :            : 
      73                 :            : static inline void
      74                 :          0 : from_json( const nlohmann::json& obj, OutboundSessionData& msg )
      75                 :            : // *****************************************************************************
      76                 :            : //! Deserialize OutboundSessionData from json format
      77                 :            : //! \param[in] obj nlohmann::json object to read from
      78                 :            : //! \param[in,out] msg OutboundSessionData message to write to
      79                 :            : // *****************************************************************************
      80                 :            : {
      81 [ -  - ][ -  - ]:          0 :   msg.session_id = obj.at( "session_id" ).get< std::string >();
                 [ -  - ]
      82 [ -  - ][ -  - ]:          0 :   msg.session_key = obj.at( "session_key" ).get< std::string >();
                 [ -  - ]
      83 [ -  - ][ -  - ]:          0 :   msg.message_index = obj.at( "message_index" ).get< uint64_t >();
                 [ -  - ]
      84                 :          0 : }
      85                 :            : 
      86                 :            : struct OutboundSessionDataRef {
      87                 :            :   OlmOutboundGroupSession* session;
      88                 :            :   OutboundSessionData data;
      89                 :            : };
      90                 :            : 
      91                 :            : struct DevKeys {
      92                 :            :   std::string ed25519;
      93                 :            :   std::string curve25519;
      94                 :            : };
      95                 :            : 
      96                 :            : static inline void
      97                 :         13 : to_json( nlohmann::json& obj, const DevKeys& msg ) 
      98                 :            : // *****************************************************************************
      99                 :            : //! Serialize DevKeys to json format
     100                 :            : //! \param[in,out] obj nlohmann::json object to write to
     101                 :            : //! \param[in] msg DevKeys message to read from
     102                 :            : // *****************************************************************************
     103                 :            : {
     104         [ +  - ]:         13 :   obj[ "ed25519" ] = msg.ed25519;
     105         [ +  - ]:         13 :   obj[ "curve25519" ] = msg.curve25519;
     106                 :         13 : }
     107                 :            : 
     108                 :            : static inline void
     109                 :          8 : from_json( const nlohmann::json& obj, DevKeys& msg )
     110                 :            : // *****************************************************************************
     111                 :            : //! Deserialize DevKeys from json format
     112                 :            : //! \param[in] obj nlohmann::json object to read from
     113                 :            : //! \param[in,out] msg DevKeys message to write to
     114                 :            : // *****************************************************************************
     115                 :            : {
     116 [ +  - ][ +  - ]:          8 :   msg.ed25519 = obj.at( "ed25519" ).get< std::string >();
                 [ +  - ]
     117 [ +  - ][ +  - ]:          8 :   msg.curve25519 = obj.at( "curve25519" ).get< std::string >();
                 [ +  - ]
     118                 :          8 : }
     119                 :            : 
     120                 :            : #if defined(__clang__)
     121                 :            :   #pragma clang diagnostic push
     122                 :            :   #pragma clang diagnostic ignored "-Wpadded"
     123                 :            : #endif
     124                 :            : 
     125                 :            : struct OlmCipherContent {
     126                 :            :   std::string body;
     127                 :            :   uint8_t type;
     128                 :            : };
     129                 :            : 
     130                 :            : #if defined(__clang__)
     131                 :            :   #pragma clang diagnostic pop
     132                 :            : #endif
     133                 :            : 
     134                 :          0 : inline void from_json(const nlohmann::json& obj, OlmCipherContent& msg )
     135                 :            : // *****************************************************************************
     136                 :            : //! Deserialize OlmCipherContent from json format
     137                 :            : //! \param[in] obj nlohmann::json object to read from
     138                 :            : //! \param[in,out] msg OlmCipherContent to write to
     139                 :            : // *****************************************************************************
     140                 :            : {
     141 [ -  - ][ -  - ]:          0 :   msg.body = obj.at("body").get< std::string >();
                 [ -  - ]
     142 [ -  - ][ -  - ]:          0 :   msg.type = obj.at("type").get< uint8_t >();
                 [ -  - ]
     143                 :          0 : }
     144                 :            : 
     145                 :            : struct OlmMessage {
     146                 :            :   std::string sender_key;
     147                 :            :   std::string sender;
     148                 :            :   using RecipientKey = std::string;
     149                 :            :   std::map< RecipientKey, OlmCipherContent > ciphertext;
     150                 :            : };
     151                 :            : 
     152                 :          0 : inline void from_json( const nlohmann::json& obj, OlmMessage& msg )
     153                 :            : // *****************************************************************************
     154                 :            : //! Deserialize OlmMessage from json format
     155                 :            : //! \param[in] obj nlohmann::json object to read from
     156                 :            : //! \param[in,out] msg OlmMessage to write to
     157                 :            : // *****************************************************************************
     158                 :            : {
     159 [ -  - ][ -  - ]:          0 :   if (obj.at("type") != "m.room.encrypted") {
                 [ -  - ]
     160         [ -  - ]:          0 :     throw std::invalid_argument( "invalid type for olm message" );
     161                 :            :   }
     162                 :            : 
     163 [ -  - ][ -  - ]:          0 :   if (obj.at("content").at("algorithm") != OLM_ALGO)
         [ -  - ][ -  - ]
                 [ -  - ]
     164         [ -  - ]:          0 :     throw std::invalid_argument("invalid algorithm for olm message");
     165                 :            : 
     166 [ -  - ][ -  - ]:          0 :   msg.sender = obj.at("sender").get< std::string >();
                 [ -  - ]
     167 [ -  - ][ -  - ]:          0 :   msg.sender_key = obj.at("content").at("sender_key").get< std::string >();
         [ -  - ][ -  - ]
                 [ -  - ]
     168 [ -  - ][ -  - ]:          0 :   msg.ciphertext = obj.at("content").at("ciphertext").
         [ -  - ][ -  - ]
     169         [ -  - ]:          0 :    get< std::map< std::string, OlmCipherContent > >();
     170                 :          0 : }
     171                 :            : 
     172                 :            : struct Storage {
     173                 :            : 
     174                 :            :   using OlmSessionPtr = mtx::crypto::OlmSessionPtr;
     175                 :            :   using InboundGroupSessionPtr = mtx::crypto::InboundGroupSessionPtr;
     176                 :            :   using OutboundGroupSessionPtr = mtx::crypto::OutboundGroupSessionPtr;
     177                 :            : 
     178                 :            :   //! Storage for the user_id -> list of devices mapping
     179                 :            :   std::map< std::string, std::vector< std::string > > devices;
     180                 :            :   //! Storage for the identity key for a device.
     181                 :            :   std::map< std::string, DevKeys > device_keys;
     182                 :            :   //! Flag that indicate if a specific room has encryption enabled.
     183                 :            :   std::map< std::string, bool > encrypted_rooms;
     184                 :            : 
     185                 :            :   //! Keep track of members per room.
     186                 :            :   std::map< std::string, std::map< std::string, bool > > members;
     187                 :            : 
     188                 :          9 :   void add_member( const std::string& room_id, const std::string& user_id ) {
     189                 :          9 :     members[room_id][user_id] = true;
     190                 :          9 :   }
     191                 :            : 
     192                 :            :   //! Mapping from curve25519 to session
     193                 :            :   std::map< std::string, OlmSessionPtr > olm_inbound_sessions;
     194                 :            :   std::map< std::string, OlmSessionPtr > olm_outbound_sessions;
     195                 :            : 
     196                 :            :   std::map< std::string, InboundGroupSessionPtr > inbound_group_sessions;
     197                 :            :   std::map< std::string, OutboundSessionData > outbound_group_session_data;
     198                 :            :   std::map< std::string, OutboundGroupSessionPtr > outbound_group_sessions;
     199                 :            : 
     200                 :          0 :   bool outbound_group_exists( const std::string& room_id ) {
     201                 :            :     return
     202         [ -  - ]:          0 :       outbound_group_sessions.find( room_id ) !=
     203 [ -  - ][ -  - ]:          0 :         end( outbound_group_sessions ) &&
     204         [ -  - ]:          0 :       outbound_group_session_data.find( room_id ) !=
     205                 :          0 :         end( outbound_group_session_data );
     206                 :            :   }
     207                 :            : 
     208                 :          0 :   void set_outbound_group_session( const std::string& room_id,
     209                 :            :                                    OutboundGroupSessionPtr session,
     210                 :            :                                    OutboundSessionData data )
     211                 :            :   {
     212                 :          0 :     outbound_group_session_data[ room_id ] = data;
     213                 :          0 :     outbound_group_sessions[ room_id ] = std::move( session );
     214                 :          0 :   }
     215                 :            : 
     216                 :            :   OutboundSessionDataRef
     217                 :          0 :   get_outbound_group_session( const std::string& room_id ) {
     218                 :          0 :     return { outbound_group_sessions[ room_id ].get(),
     219                 :          0 :              outbound_group_session_data[ room_id ] };
     220                 :            :   }
     221                 :            : 
     222                 :          0 :   bool inbound_group_exists( const std::string& room_id,
     223                 :            :                              const std::string& session_id,
     224                 :            :                              const std::string& sender_key )
     225                 :            :   {
     226 [ -  - ][ -  - ]:          0 :     const auto key = room_id + session_id + sender_key;
     227         [ -  - ]:          0 :     return inbound_group_sessions.find( key ) != end(inbound_group_sessions);
     228                 :            :   }
     229                 :            : 
     230                 :          0 :   void set_inbound_group_session( const std::string& room_id,
     231                 :            :                                   const std::string& session_id,
     232                 :            :                                   const std::string& sender_key,
     233                 :            :                                   InboundGroupSessionPtr session )
     234                 :            :   {
     235 [ -  - ][ -  - ]:          0 :     const auto key = room_id + session_id + sender_key;
     236         [ -  - ]:          0 :     inbound_group_sessions[ key ] = std::move( session );
     237                 :          0 :   }
     238                 :            : 
     239                 :            :   OlmInboundGroupSession*
     240                 :          0 :     get_inbound_group_session( const std::string& room_id,
     241                 :            :                                const std::string& session_id,
     242                 :            :                               const std::string& sender_key )
     243                 :            :   {
     244 [ -  - ][ -  - ]:          0 :     const auto key = room_id + session_id + sender_key;
     245         [ -  - ]:          0 :     return inbound_group_sessions[ key ].get();
     246                 :            :   }
     247                 :            : 
     248                 :          3 :   void load() {
     249                 :            :     using namespace std;
     250                 :            :     using namespace mtx::crypto;
     251 [ +  - ][ +  - ]:          3 :     MDEBUG( "matrix session restoring storage" );
         [ +  - ][ +  - ]
     252                 :            : 
     253         [ +  - ]:          3 :     ifstream db( g_mtx_session_db_filename );
     254 [ +  - ][ -  + ]:          3 :     if (not db.is_open()) {
     255 [ -  - ][ -  - ]:          0 :       MINFO( "couldn't open matrix session db " << g_mtx_session_db_filename );
         [ -  - ][ -  - ]
                 [ -  - ]
     256                 :          0 :       return;
     257                 :            :     }
     258                 :          3 :     string db_data( (istreambuf_iterator< char >( db )),
     259         [ +  - ]:          6 :                      istreambuf_iterator< char >() );
     260                 :            : 
     261         [ -  + ]:          3 :     if (db_data.empty()) return;
     262                 :            : 
     263         [ +  - ]:          6 :     nlohmann::json obj = nlohmann::json::parse( db_data );
     264                 :            : 
     265 [ +  - ][ +  - ]:          3 :     devices = obj.at( "devices" ).get< map< string, vector< string > > >();
                 [ +  - ]
     266 [ +  - ][ +  - ]:          3 :     device_keys = obj.at( "device_keys" ).get< map< string, DevKeys > >();
                 [ +  - ]
     267 [ +  - ][ +  - ]:          3 :     encrypted_rooms = obj.at( "encrypted_rooms" ).get< map< string, bool> >();
                 [ +  - ]
     268 [ +  - ][ +  - ]:          3 :     members = obj.at( "members" ).get< map< string, map< string, bool > > >();
                 [ +  - ]
     269                 :            : 
     270 [ +  - ][ -  + ]:          3 :     if (obj.count( "olm_inbound_sessions" ) != 0) {
     271 [ -  - ][ -  - ]:          0 :       auto sessions = obj.at( "olm_inbound_sessions" ).get<map<string, string>>();
                 [ -  - ]
     272         [ -  - ]:          0 :       for (const auto &s : sessions)
     273         [ -  - ]:          0 :         olm_inbound_sessions[ s.first ] =
     274         [ -  - ]:          0 :           unpickle< SessionObject >( s.second, g_mtx_db_storage_key );
     275                 :            :     }
     276                 :            : 
     277 [ +  - ][ -  + ]:          3 :     if (obj.count( "olm_outbound_sessions" ) != 0) {
     278 [ -  - ][ -  - ]:          0 :       auto sessions = obj.at("olm_outbound_sessions").get<map<string,string>>();
                 [ -  - ]
     279         [ -  - ]:          0 :       for (const auto &s : sessions)
     280         [ -  - ]:          0 :         olm_outbound_sessions[ s.first ] =
     281         [ -  - ]:          0 :           unpickle< SessionObject >( s.second, g_mtx_db_storage_key );
     282                 :            :     }
     283                 :            : 
     284 [ +  - ][ -  + ]:          3 :     if (obj.count( "inbound_group_sessions" ) != 0) {
     285 [ -  - ][ -  - ]:          0 :       auto sessions = obj.at("inbound_group_sessions").get<map<string, string>>();
                 [ -  - ]
     286         [ -  - ]:          0 :       for (const auto &s : sessions)
     287         [ -  - ]:          0 :         inbound_group_sessions[ s.first ] =
     288         [ -  - ]:          0 :           unpickle< InboundSessionObject >( s.second, g_mtx_db_storage_key );
     289                 :            :     }
     290                 :            : 
     291 [ +  - ][ -  + ]:          3 :     if (obj.count( "outbound_group_sessions" ) != 0) {
     292 [ -  - ][ -  - ]:          0 :       auto sessions = obj.at("outbound_group_sessions").get<map<string, string>>();
                 [ -  - ]
     293         [ -  - ]:          0 :       for (const auto &s : sessions)
     294         [ -  - ]:          0 :         outbound_group_sessions[ s.first ] =
     295         [ -  - ]:          0 :           unpickle< OutboundSessionObject >( s.second, g_mtx_db_storage_key );
     296                 :            :     }
     297                 :            : 
     298 [ +  - ][ -  + ]:          3 :     if (obj.count( "outbound_group_session_data" ) != 0) {
     299 [ -  - ][ -  - ]:          0 :       auto sessions = obj.at( "outbound_group_session_data" ).
     300         [ -  - ]:          0 :         get<map<string,OutboundSessionData>>();
     301         [ -  - ]:          0 :       for (const auto &s : sessions)
     302 [ -  - ][ -  - ]:          0 :         outbound_group_session_data[s.first] = s.second;
     303                 :            :     }
     304                 :            :   }
     305                 :            : 
     306                 :          3 :   void save() {
     307                 :            :     using namespace mtx::crypto;
     308 [ +  - ][ +  - ]:          3 :     MDEBUG( "matrix session saving storage" );
         [ +  - ][ +  - ]
     309                 :            : 
     310         [ +  - ]:          3 :     std::ofstream db( g_mtx_session_db_filename );
     311 [ +  - ][ -  + ]:          3 :     if (not db.is_open()) {
     312 [ -  - ][ -  - ]:          0 :       MERROR( "Couldn't open matrix session db " << g_mtx_session_db_filename );
         [ -  - ][ -  - ]
                 [ -  - ]
     313                 :          0 :       return;
     314                 :            :     }
     315                 :            : 
     316                 :          6 :     nlohmann::json data;
     317 [ +  - ][ +  - ]:          3 :     data[ "devices" ] = devices;
     318 [ +  - ][ +  - ]:          3 :     data[ "device_keys" ] = device_keys;
     319 [ +  - ][ +  - ]:          3 :     data[ "encrypted_rooms" ] = encrypted_rooms;
     320 [ +  - ][ +  - ]:          3 :     data[ "members" ] = members;
     321                 :            : 
     322                 :            :     // Save inbound sessions
     323         [ -  + ]:          3 :     for (const auto &s : olm_inbound_sessions)
     324 [ -  - ][ -  - ]:          0 :       data[ "olm_inbound_sessions" ][ s.first ] =
     325 [ -  - ][ -  - ]:          0 :         pickle< SessionObject >( s.second.get(), g_mtx_db_storage_key );
     326                 :            : 
     327         [ -  + ]:          3 :     for (const auto &s : olm_outbound_sessions)
     328 [ -  - ][ -  - ]:          0 :       data[ "olm_outbound_sessions" ][ s.first ] =
     329 [ -  - ][ -  - ]:          0 :         pickle< SessionObject >( s.second.get(), g_mtx_db_storage_key );
     330                 :            : 
     331         [ -  + ]:          3 :     for (const auto &s : inbound_group_sessions)
     332 [ -  - ][ -  - ]:          0 :       data[ "inbound_group_sessions" ][ s.first ] =
     333 [ -  - ][ -  - ]:          0 :         pickle< InboundSessionObject >( s.second.get(), g_mtx_db_storage_key );
     334                 :            : 
     335         [ -  + ]:          3 :     for (const auto &s : outbound_group_sessions)
     336 [ -  - ][ -  - ]:          0 :       data[ "outbound_group_sessions" ][ s.first ] =
     337 [ -  - ][ -  - ]:          0 :         pickle< OutboundSessionObject >( s.second.get(), g_mtx_db_storage_key );
     338                 :            : 
     339         [ -  + ]:          3 :     for (const auto &s : outbound_group_session_data)
     340 [ -  - ][ -  - ]:          0 :       data[ "outbound_group_session_data" ][ s.first ] = s.second;
                 [ -  - ]
     341                 :            : 
     342                 :            :     // Save to file
     343 [ +  - ][ +  - ]:          3 :     db << data.dump( 2 );
     344         [ +  - ]:          3 :     db.close();
     345                 :            :   }
     346                 :            : };
     347                 :            : 
     348                 :            : static Storage g_storage;
     349                 :            : 
     350                 :            : static void
     351                 :          1 : print_errors( mtx::http::RequestErr err )
     352                 :            : // *****************************************************************************
     353                 :            : //! Log matrix request errors
     354                 :            : //! \param[in] err Matrix error object
     355                 :            : // *****************************************************************************
     356                 :            : {
     357         [ +  - ]:          1 :   if (err->status_code)
     358 [ +  - ][ +  - ]:          1 :     MERROR( "status code: " << static_cast<uint16_t>(err->status_code));
         [ +  - ][ +  - ]
     359         [ -  + ]:          1 :   if (not err->matrix_error.error.empty() )
     360 [ -  - ][ -  - ]:          0 :     MERROR( "error msg: " << err->matrix_error.error );
         [ -  - ][ -  - ]
     361         [ +  - ]:          1 :   if (err->error_code)
     362 [ +  - ][ +  - ]:          1 :     MERROR( "error code: " << err->error_code );
         [ +  - ][ +  - ]
     363         [ +  - ]:          1 :   if (not err->parse_error.empty())
     364 [ +  - ][ +  - ]:          1 :     MERROR( "parse error: " << err->parse_error );
         [ +  - ][ +  - ]
     365                 :          1 : }
     366                 :            : 
     367                 :            : static void
     368                 :          0 : decrypt_olm_message( const OlmMessage& olm_msg )
     369                 :            : // *****************************************************************************
     370                 :            : //! Decrypt Olm message
     371                 :            : //! \param[in] olm_msg Message to decrypt
     372                 :            : // *****************************************************************************
     373                 :            : {
     374 [ -  - ][ -  - ]:          0 :   MINFO( "OLM message" );
         [ -  - ][ -  - ]
     375 [ -  - ][ -  - ]:          0 :   MINFO( "sender: " << olm_msg.sender );
         [ -  - ][ -  - ]
                 [ -  - ]
     376 [ -  - ][ -  - ]:          0 :   MINFO( "sender_key: " << olm_msg.sender_key );
         [ -  - ][ -  - ]
                 [ -  - ]
     377                 :            : 
     378         [ -  - ]:          0 :   const auto my_id_key = g_olmclient->identity_keys().curve25519;
     379         [ -  - ]:          0 :   for (const auto& cipher : olm_msg.ciphertext) {
     380         [ -  - ]:          0 :     if (cipher.first == my_id_key) {
     381         [ -  - ]:          0 :       const auto msg_body = cipher.second.body;
     382                 :          0 :       const auto msg_type = cipher.second.type;
     383                 :            : 
     384 [ -  - ][ -  - ]:          0 :       MINFO( "the message is meant for us" );
         [ -  - ][ -  - ]
     385 [ -  - ][ -  - ]:          0 :       MINFO( "body: " << msg_body );
         [ -  - ][ -  - ]
                 [ -  - ]
     386 [ -  - ][ -  - ]:          0 :       MINFO( "type: " << msg_type );
         [ -  - ][ -  - ]
                 [ -  - ]
     387                 :            : 
     388         [ -  - ]:          0 :       if (msg_type == 0) {
     389 [ -  - ][ -  - ]:          0 :         MINFO( "opening session with " << olm_msg.sender );
         [ -  - ][ -  - ]
                 [ -  - ]
     390         [ -  - ]:          0 :         auto inbound_session = g_olmclient->create_inbound_session( msg_body );
     391                 :            : 
     392         [ -  - ]:          0 :         auto ok = mtx::crypto::matches_inbound_session_from(
     393                 :          0 :           inbound_session.get(), olm_msg.sender_key, msg_body );
     394                 :            : 
     395         [ -  - ]:          0 :         if (not ok) {
     396 [ -  - ][ -  - ]:          0 :           MERROR( "session could not be established" );
         [ -  - ][ -  - ]
     397                 :            :         } else {
     398                 :            :           auto output = g_olmclient->decrypt_message( inbound_session.get(),
     399                 :            :                                                       msg_type,
     400         [ -  - ]:          0 :                                                       msg_body );
     401                 :            : 
     402                 :            :           auto plaintext = nlohmann::json::parse(
     403 [ -  - ][ -  - ]:          0 :             std::string( begin(output), end(output) ) );
     404 [ -  - ][ -  - ]:          0 :           MINFO( "decrypted message: " << plaintext.dump(2) );
         [ -  - ][ -  - ]
         [ -  - ][ -  - ]
     405                 :            : 
     406                 :          0 :           g_storage.olm_inbound_sessions.emplace( olm_msg.sender_key,
     407         [ -  - ]:          0 :                                                   std::move( inbound_session ) );
     408                 :            : 
     409                 :            :           std::string room_id =
     410 [ -  - ][ -  - ]:          0 :             plaintext.at( "content" ).at( "room_id" ).get< std::string >();
         [ -  - ][ -  - ]
                 [ -  - ]
     411                 :            :           std::string session_id =
     412 [ -  - ][ -  - ]:          0 :             plaintext.at( "content ").at( "session_id" ).get <std::string >();
         [ -  - ][ -  - ]
                 [ -  - ]
     413                 :            :           std::string session_key =
     414 [ -  - ][ -  - ]:          0 :             plaintext.at( "content" ).at( "session_key" ).get <std::string >();
         [ -  - ][ -  - ]
                 [ -  - ]
     415                 :            : 
     416         [ -  - ]:          0 :           if (g_storage.inbound_group_exists( room_id, session_id,
     417         [ -  - ]:          0 :                                               olm_msg.sender_key ))
     418                 :            :           {
     419 [ -  - ][ -  - ]:          0 :             MWARNING( "megolm session already exists" );
         [ -  - ][ -  - ]
     420                 :            :           } else {
     421                 :            :             auto megolm_session =
     422         [ -  - ]:          0 :               g_olmclient->init_inbound_group_session( session_key );
     423                 :          0 :             g_storage.set_inbound_group_session( room_id,
     424                 :            :                                                  session_id,
     425         [ -  - ]:          0 :                                                  olm_msg.sender_key,
     426                 :          0 :                                                  std::move( megolm_session ) );
     427 [ -  - ][ -  - ]:          0 :             MINFO( "megolm_session saved" );
         [ -  - ][ -  - ]
     428                 :            :           }
     429                 :            :         }
     430                 :            :       }
     431                 :            :     }
     432                 :            :   }
     433                 :          0 : }
     434                 :            : 
     435                 :            : static void
     436                 :         13 : handle_to_device_msgs( const mtx::responses::ToDevice& msgs )
     437                 :            : // *****************************************************************************
     438                 :            : //! Handle to-device messages
     439                 :            : //! \param[in] msgs Messages to handle
     440                 :            : // *****************************************************************************
     441                 :            : {
     442         [ -  + ]:         13 :   if (not msgs.events.empty()) {
     443 [ -  - ][ -  - ]:          0 :     MINFO( "inspecting to_device messages " << msgs.events.size() );
         [ -  - ][ -  - ]
     444                 :            :   }
     445                 :            : 
     446         [ -  + ]:         13 :   for (const auto& msg : msgs.events) {
     447 [ -  - ][ -  - ]:          0 :     MINFO( std::visit(
         [ -  - ][ -  - ]
         [ -  - ][ -  - ]
     448                 :            :       [](const auto& e){ return nlohmann::json(e); }, msg ).dump( 2 ) );
     449                 :            : 
     450                 :            :     try {
     451                 :            :       OlmMessage olm_msg = std::visit(
     452         [ -  - ]:          0 :         [](const auto &e){ return nlohmann::json(e).get< OlmMessage >(); }, msg
     453         [ -  - ]:          0 :       );
     454         [ -  - ]:          0 :       decrypt_olm_message( std::move( olm_msg ) );
     455    [ -  - ][ - ]:          0 :     } catch ( const nlohmann::json::exception& e ) {
     456 [ -  - ][ -  - ]:          0 :       MWARNING( "parsing error for olm message: " << e.what() );
         [ -  - ][ -  - ]
                 [ -  - ]
     457                 :          0 :     } catch ( const std::invalid_argument& e ) {
     458 [ -  - ][ -  - ]:          0 :       MWARNING( "validation error for olm message: " << e.what() );
         [ -  - ][ -  - ]
                 [ -  - ]
     459                 :            :     }
     460                 :            :   }
     461                 :         13 : }
     462                 :            : 
     463                 :            : static void
     464                 :          0 : keys_uploaded_cb( const mtx::responses::UploadKeys&, mtx::http::RequestErr err )
     465                 :            : // *****************************************************************************
     466                 :            : // *****************************************************************************
     467                 :            : {
     468         [ -  - ]:          0 :  if (err) {
     469                 :          0 :    print_errors( err );
     470                 :          0 :    return;
     471                 :            :  }
     472                 :          0 :  g_olmclient->mark_keys_as_published();
     473 [ -  - ][ -  - ]:          0 :  MINFO( "keys uploaded" );
                 [ -  - ]
     474                 :            : }
     475                 :            : 
     476                 :            : template< class T >
     477                 :         31 : static bool is_room_encryption( const T& event )
     478                 :            : // *****************************************************************************
     479                 :            : // *****************************************************************************
     480                 :            : {
     481                 :            :   using namespace mtx::events;
     482                 :            :   using namespace mtx::events::state;
     483                 :         31 :   return std::holds_alternative< StateEvent< Encryption > >( event );
     484                 :            : }
     485                 :            : 
     486                 :            : template< class T >
     487                 :            : static std::string
     488                 :          0 : get_json( const T& event )
     489                 :            : // *****************************************************************************
     490                 :            : // *****************************************************************************
     491                 :            : {
     492         [ -  - ]:          0 :   return std::visit( [](auto e){ return nlohmann::json(e).dump(2); }, event );
     493                 :            : }
     494                 :            : 
     495                 :            : static void
     496                 :          0 : mark_encrypted_room( const RoomId& id )
     497                 :            : // *****************************************************************************
     498                 :            : // *****************************************************************************
     499                 :            : {
     500 [ -  - ][ -  - ]:          0 :   MINFO( "encryption is enabled for room: " << id.get() );
         [ -  - ][ -  - ]
     501                 :          0 :   g_storage.encrypted_rooms[ id.get() ] = true;
     502                 :          0 : }
     503                 :            : 
     504                 :            : template<class T>
     505                 :            : static bool
     506                 :         31 : is_member_event( const T& event )
     507                 :            : // *****************************************************************************
     508                 :            : // *****************************************************************************
     509                 :            : {
     510                 :            :   using namespace mtx::events;
     511                 :            :   using namespace mtx::events::state;
     512                 :         31 :   return std::holds_alternative< StateEvent< Member > >( event );
     513                 :            : }
     514                 :            : 
     515                 :            : static bool
     516                 :         21 : is_encrypted( const mtx::events::collections::TimelineEvents& event )
     517                 :            : // *****************************************************************************
     518                 :            : // *****************************************************************************
     519                 :            : {
     520                 :            :   using mtx::events::EncryptedEvent;
     521                 :            :   using mtx::events::msg::Encrypted;
     522                 :         21 :   return std::holds_alternative< EncryptedEvent< Encrypted > >( event );
     523                 :            : }
     524                 :            : 
     525                 :            : template<class Container, class Item>
     526                 :            : static bool
     527                 :         27 : exists( const Container& container, const Item& item )
     528                 :            : // *****************************************************************************
     529                 :            : // *****************************************************************************
     530                 :            : {
     531         [ +  - ]:         27 :   return container.find(item) != end(container);
     532                 :            : }
     533                 :            : 
     534                 :            : static void
     535                 :          9 : save_device_keys( const mtx::responses::QueryKeys& res )
     536                 :            : // *****************************************************************************
     537                 :            : // *****************************************************************************
     538                 :            : {
     539         [ +  + ]:         18 :   for (const auto &entry : res.device_keys) {
     540         [ +  - ]:         18 :     const auto user_id = entry.first;
     541                 :            : 
     542 [ +  - ][ -  + ]:          9 :     if (not exists( g_storage.devices, user_id ))
     543 [ -  - ][ -  - ]:          0 :       MINFO( "keys for " << user_id );
         [ -  - ][ -  - ]
                 [ -  - ]
     544                 :            : 
     545                 :         18 :     std::vector< std::string > device_list;
     546         [ +  + ]:         18 :     for (const auto &device : entry.second) {
     547         [ +  - ]:          9 :       const auto key_struct = device.second;
     548                 :            : 
     549         [ +  - ]:          9 :       const std::string device_id = key_struct.device_id;
     550         [ +  - ]:          9 :       const std::string index = "curve25519:" + device_id;
     551                 :            : 
     552 [ +  - ][ -  + ]:          9 :       if (key_struct.keys.find(index) == end(key_struct.keys)) continue;
     553                 :            : 
     554 [ +  - ][ +  - ]:         18 :       const auto key = key_struct.keys.at( index );
     555                 :            : 
     556 [ +  - ][ +  + ]:          9 :       if (!exists(g_storage.device_keys, device_id)) {
     557 [ +  - ][ +  - ]:          5 :         MINFO( "save device id => key: " << device_id << " => " << key );
         [ +  - ][ +  - ]
         [ +  - ][ +  - ]
                 [ +  - ]
     558         [ +  - ]:          5 :         g_storage.device_keys[ device_id ] =
     559 [ +  - ][ +  - ]:         10 :           { key_struct.keys.at("ed25519:" + device_id),
     560 [ +  - ][ +  - ]:         10 :             key_struct.keys.at("curve25519:" + device_id) };
         [ +  - ][ +  - ]
     561                 :            :       }
     562                 :            : 
     563         [ +  - ]:          9 :       device_list.push_back( device_id );
     564                 :            :     }
     565                 :            : 
     566 [ +  - ][ -  + ]:          9 :     if (not exists( g_storage.devices, user_id )) {
     567 [ -  - ][ -  - ]:          0 :       g_storage.devices[ user_id ] = device_list;
     568                 :            :     }
     569                 :            :   }
     570                 :          9 : }
     571                 :            : 
     572                 :            : static void
     573                 :          9 : get_device_keys( const UserId& user )
     574                 :            : // *****************************************************************************
     575                 :            : // *****************************************************************************
     576                 :            : {
     577                 :            :   // Retrieve all devices keys
     578                 :          9 :   mtx::requests::QueryKeys query;
     579 [ +  - ][ +  - ]:          9 :   query.device_keys[ user.get() ] = {};
     580                 :            : 
     581         [ +  - ]:         18 :   g_mtxclient->query_keys( query,
     582                 :          9 :     [](const mtx::responses::QueryKeys &res, mtx::http::RequestErr err) {
     583         [ -  + ]:          9 :       if (err) {
     584                 :          0 :         print_errors( err );
     585                 :          0 :         return;
     586                 :            :       }
     587                 :            : 
     588         [ +  + ]:         18 :       for (const auto& key : res.device_keys) {
     589         [ +  - ]:         18 :         const auto user_id = key.first;
     590         [ +  - ]:         18 :         const auto devices = key.second;
     591                 :            : 
     592         [ +  + ]:         18 :         for (const auto &device : devices) {
     593         [ +  - ]:         18 :           const auto id = device.first;
     594         [ +  - ]:         18 :           const auto data = device.second;
     595                 :            : 
     596                 :            :           try {
     597         [ +  - ]:          9 :             auto ok = verify_identity_signature(
     598 [ +  - ][ +  - ]:         18 :               nlohmann::json( data ).get< mtx::crypto::DeviceKeys >(),
     599 [ +  - ][ +  - ]:         18 :               DeviceId( id ), UserId( user_id ) );
     600                 :            : 
     601         [ -  + ]:          9 :             if (not ok) {
     602 [ -  - ][ -  - ]:          0 :               MWARNING( "signature could not be verified" );
         [ -  - ][ -  - ]
     603 [ -  - ][ -  - ]:          0 :               MWARNING( nlohmann::json( data ).dump( 2 ) );
         [ -  - ][ -  - ]
         [ -  - ][ -  - ]
     604                 :            :             }
     605 [ -  - ][ -  - ]:          0 :           } catch (const mtx::crypto::olm_exception &e) {
     606 [ -  - ][ -  - ]:          0 :             MWARNING( "exception: " << e.what() );
         [ -  - ][ -  - ]
                 [ -  - ]
     607                 :            :           }
     608                 :            :         }
     609                 :            :       }
     610                 :            : 
     611                 :          9 :       save_device_keys( std::move(res) );
     612         [ +  - ]:         18 :   } );
     613                 :          9 : }
     614                 :            : 
     615                 :            : static void
     616                 :          0 : send_group_message( OlmOutboundGroupSession* session,
     617                 :            :                     const std::string& session_id,
     618                 :            :                     const std::string& room_id,
     619                 :            :                     const std::string& msg )
     620                 :            : // *****************************************************************************
     621                 :            : // *****************************************************************************
     622                 :            : {
     623                 :            :   // Create event payload
     624                 :            :   nlohmann::json doc{ {"type", "m.room.message"},
     625                 :            :                       {"content", {{"type", "m.text"}, {"body", msg}}},
     626 [ -  - ][ -  - ]:          0 :                       {"room_id", room_id} };
         [ -  - ][ -  - ]
         [ -  - ][ -  - ]
         [ -  - ][ -  - ]
         [ -  - ][ -  - ]
         [ -  - ][ -  - ]
         [ -  - ][ -  - ]
         [ -  - ][ -  - ]
     627                 :            : 
     628 [ -  - ][ -  - ]:          0 :   auto payload = g_olmclient->encrypt_group_message( session, doc.dump() );
     629                 :            : 
     630                 :            :   using namespace mtx::events;
     631                 :            :   using namespace mtx::identifiers;
     632                 :            :   using namespace mtx::http;
     633                 :            : 
     634                 :          0 :   msg::Encrypted data;
     635         [ -  - ]:          0 :   data.ciphertext = std::string( begin(payload), end(payload) );
     636         [ -  - ]:          0 :   data.sender_key = g_olmclient->identity_keys().curve25519;
     637         [ -  - ]:          0 :   data.session_id = session_id;
     638         [ -  - ]:          0 :   data.device_id = g_mtxclient->device_id();
     639         [ -  - ]:          0 :   data.algorithm = OLM_ALGO;
     640                 :            : 
     641         [ -  - ]:          0 :   g_mtxclient->send_room_message< msg::Encrypted >(
     642                 :          0 :     room_id, data, [](const mtx::responses::EventId& res, RequestErr err) {
     643         [ -  - ]:          0 :       if (err) {
     644                 :          0 :         print_errors( err );
     645                 :          0 :         return;
     646                 :            :       }
     647 [ -  - ][ -  - ]:          0 :       MINFO("message sent with event_id: " << res.event_id.to_string());
         [ -  - ][ -  - ]
                 [ -  - ]
     648         [ -  - ]:          0 :     } );
     649                 :          0 : }
     650                 :            : 
     651                 :            : #if defined(__clang__)
     652                 :            :   #pragma clang diagnostic push
     653                 :            :   #pragma clang diagnostic ignored "-Wpadded"
     654                 :            : #endif
     655                 :            : 
     656                 :            : static void
     657                 :          0 : create_outbound_megolm_session( const std::string& room_id,
     658                 :            :                                 const std::string& reply_msg )
     659                 :            : // *****************************************************************************
     660                 :            : // *****************************************************************************
     661                 :            : {
     662                 :            :   // Create an outbound session
     663         [ -  - ]:          0 :   auto outbound_session = g_olmclient->init_outbound_group_session();
     664                 :            : 
     665         [ -  - ]:          0 :   const auto session_id  = mtx::crypto::session_id( outbound_session.get() );
     666         [ -  - ]:          0 :   const auto session_key = mtx::crypto::session_key( outbound_session.get() );
     667                 :            : 
     668                 :          0 :   mtx::events::DeviceEvent< mtx::events::msg::RoomKey > megolm_payload;
     669         [ -  - ]:          0 :   megolm_payload.content.algorithm = OLM_ALGO;
     670         [ -  - ]:          0 :   megolm_payload.content.room_id = room_id;
     671         [ -  - ]:          0 :   megolm_payload.content.session_id = session_id;
     672         [ -  - ]:          0 :   megolm_payload.content.session_key = session_key;
     673                 :          0 :   megolm_payload.type = mtx::events::EventType::RoomKey;
     674                 :            : 
     675 [ -  - ][ -  - ]:          0 :   if (g_storage.members.find(room_id) == end(g_storage.members)) {
     676 [ -  - ][ -  - ]:          0 :     MERROR( "no members found for room " << room_id );
         [ -  - ][ -  - ]
                 [ -  - ]
     677                 :          0 :     return;
     678                 :            :   }
     679                 :            : 
     680 [ -  - ][ -  - ]:          0 :   const auto members = g_storage.members[ room_id ];
     681                 :            : 
     682         [ -  - ]:          0 :   for (const auto &member : members) {
     683 [ -  - ][ -  - ]:          0 :     const auto devices = g_storage.devices[ member.first ];
     684                 :            : 
     685                 :            :     // TODO: Figure out for which devices we don't have olm sessions.
     686         [ -  - ]:          0 :     for (const auto &dev : devices) {
     687                 :            :       // TODO: check if we have downloaded the keys
     688 [ -  - ][ -  - ]:          0 :       const auto device_keys = g_storage.device_keys[ dev ];
     689                 :            : 
     690                 :          0 :       auto to_device_cb = [](mtx::http::RequestErr err) {
     691         [ -  - ]:          0 :         if (err) {
     692                 :          0 :           print_errors( err );
     693                 :            :         }
     694                 :          0 :       };
     695                 :            : 
     696         [ -  - ]:          0 :       if (g_storage.olm_outbound_sessions.find(device_keys.curve25519) !=
     697         [ -  - ]:          0 :           end(g_storage.olm_outbound_sessions))
     698                 :            :       {
     699 [ -  - ][ -  - ]:          0 :         MINFO("found existing olm outbound session with device" << dev );
         [ -  - ][ -  - ]
                 [ -  - ]
     700                 :            :         auto olm_session =
     701         [ -  - ]:          0 :           g_storage.olm_outbound_sessions[ device_keys.curve25519 ].get();
     702                 :            : 
     703                 :            :         auto device_msg =
     704                 :            :           g_olmclient->create_olm_encrypted_content( olm_session,
     705                 :            :                                                      megolm_payload,
     706         [ -  - ]:          0 :                                                      UserId( member.first ),
     707                 :            :                                                      device_keys.ed25519,
     708 [ -  - ][ -  - ]:          0 :                                                      device_keys.curve25519 );
     709                 :            : 
     710 [ -  - ][ -  - ]:          0 :         nlohmann::json body{{"messages", {{member, {{dev, device_msg}}}}}};
         [ -  - ][ -  - ]
         [ -  - ][ -  - ]
         [ -  - ][ -  - ]
         [ -  - ][ -  - ]
     711 [ -  - ][ -  - ]:          0 :         g_mtxclient->send_to_device("m.room.encrypted", body, to_device_cb);
                 [ -  - ]
     712                 :            :         // TODO: send message to device
     713                 :            :       } else {
     714 [ -  - ][ -  - ]:          0 :         MINFO( "claiming one time keys for device " << dev );
         [ -  - ][ -  - ]
                 [ -  - ]
     715                 :            :         using mtx::responses::ClaimKeys;
     716                 :            :         using mtx::http::RequestErr;
     717                 :          0 :         auto cb = [member = member.first, dev, megolm_payload, to_device_cb](
     718                 :            :                     const ClaimKeys &res, RequestErr err) {
     719         [ -  - ]:          0 :           if (err) {
     720         [ -  - ]:          0 :             print_errors(err);
     721                 :          0 :             return;
     722                 :            :           }
     723                 :            : 
     724 [ -  - ][ -  - ]:          0 :           MINFO( "claimed keys for member - dev: " << member << " - " << dev );
         [ -  - ][ -  - ]
         [ -  - ][ -  - ]
                 [ -  - ]
     725 [ -  - ][ -  - ]:          0 :           MINFO( "room_key " << nlohmann::json(megolm_payload).dump(4) );
         [ -  - ][ -  - ]
         [ -  - ][ -  - ]
                 [ -  - ]
     726                 :            : 
     727 [ -  - ][ -  - ]:          0 :           MWARNING( "signed one time keys" );
         [ -  - ][ -  - ]
     728 [ -  - ][ -  - ]:          0 :           auto retrieved_devices = res.one_time_keys.at( member );
     729         [ -  - ]:          0 :           for (const auto &rd : retrieved_devices) {
     730 [ -  - ][ -  - ]:          0 :             MINFO( "devices: " << rd.first << " : \n " << rd.second.dump(2) );
         [ -  - ][ -  - ]
         [ -  - ][ -  - ]
         [ -  - ][ -  - ]
     731                 :            : 
     732                 :            :             // TODO: Verify signatures
     733 [ -  - ][ -  - ]:          0 :             auto otk = rd.second.begin()->at("key");
         [ -  - ][ -  - ]
     734 [ -  - ][ -  - ]:          0 :             auto id_key = g_storage.device_keys[dev].curve25519;
     735                 :            : 
     736                 :            :             auto session = g_olmclient->create_outbound_session( id_key,
     737 [ -  - ][ -  - ]:          0 :               otk.get< std::string >() );
     738                 :            : 
     739                 :            :             auto device_msg = g_olmclient->create_olm_encrypted_content(
     740                 :            :               session.get(),
     741                 :          0 :               megolm_payload,
     742                 :          0 :               UserId(member),
     743         [ -  - ]:          0 :               g_storage.device_keys[dev].ed25519,
     744 [ -  - ][ -  - ]:          0 :               g_storage.device_keys[dev].curve25519 );
         [ -  - ][ -  - ]
     745                 :            : 
     746                 :            :             // TODO: saving should happen when the message is sent
     747         [ -  - ]:          0 :             g_storage.olm_outbound_sessions[ id_key ] = std::move(session);
     748                 :            : 
     749 [ -  - ][ -  - ]:          0 :             nlohmann::json body{{"messages", {{member, {{dev, device_msg}}}}}};
         [ -  - ][ -  - ]
         [ -  - ][ -  - ]
         [ -  - ][ -  - ]
         [ -  - ][ -  - ]
     750                 :            : 
     751 [ -  - ][ -  - ]:          0 :             g_mtxclient->send_to_device("m.room.encrypted", body, to_device_cb);
                 [ -  - ]
     752                 :            :           }
     753 [ -  - ][ -  - ]:          0 :         };
                 [ -  - ]
     754                 :            : 
     755                 :          0 :         mtx::requests::ClaimKeys claim_keys;
     756 [ -  - ][ -  - ]:          0 :         claim_keys.one_time_keys[ member.first ][ dev ] =
     757         [ -  - ]:          0 :           mtx::crypto::SIGNED_CURVE25519;
     758                 :            : 
     759                 :            :         // TODO: we should bulk request device keys here
     760 [ -  - ][ -  - ]:          0 :         g_mtxclient->claim_keys(claim_keys, cb);
                 [ -  - ]
     761                 :            :       }
     762                 :            :     }
     763                 :            :   }
     764                 :            : 
     765 [ -  - ][ -  - ]:          0 :   MINFO( "waiting to send sendToDevice messages" );
         [ -  - ][ -  - ]
     766         [ -  - ]:          0 :   std::this_thread::sleep_for( std::chrono::milliseconds(2000) );
     767 [ -  - ][ -  - ]:          0 :   MINFO( "sending encrypted group message" );
         [ -  - ][ -  - ]
     768                 :            : 
     769                 :            :   // TODO: This should be done after all sendToDevice messages have been sent.
     770         [ -  - ]:          0 :   send_group_message( outbound_session.get(), session_id, room_id, reply_msg );
     771                 :            : 
     772                 :            :   // TODO: save message index also.
     773 [ -  - ][ -  - ]:          0 :   g_storage.set_outbound_group_session(
                 [ -  - ]
     774                 :          0 :     room_id, std::move(outbound_session), {session_id, session_key} );
     775                 :            : }
     776                 :            : 
     777                 :            : #if defined(__clang__)
     778                 :            :   #pragma clang diagnostic pop
     779                 :            : #endif
     780                 :            : 
     781                 :            : static void
     782                 :          1 : invite_room( const std::string& src_user,
     783                 :            :              const std::string& target_user,
     784                 :            :              const std::string& ad )
     785                 :            : // *****************************************************************************
     786                 :            : //  Invite user to a room
     787                 :            : // *****************************************************************************
     788                 :            : {
     789 [ +  - ][ +  - ]:          1 :   MINFO( src_user << " invites " << target_user << " to room" );
         [ +  - ][ +  - ]
         [ +  - ][ +  - ]
                 [ +  - ]
     790                 :            : 
     791                 :          1 :   mtx::requests::CreateRoom req;
     792 [ +  - ][ +  - ]:          1 :   req.name   = "Conversation between " + src_user + " and " + target_user;
                 [ +  - ]
     793         [ +  - ]:          1 :   req.topic  = ad;
     794 [ +  - ][ +  - ]:          2 :   req.invite = { '@' + target_user + ':' + g_mtxclient->server() };
         [ +  - ][ +  - ]
         [ +  - ][ +  + ]
                 [ -  - ]
     795         [ +  - ]:          2 :   g_mtxclient->create_room( req,
     796                 :          1 :     [&](const mtx::responses::CreateRoom& res, mtx::http::RequestErr err) {
     797         [ +  - ]:          1 :         print_errors( err );
     798         [ +  - ]:          2 :         auto room_id = res.room_id.to_string();
     799 [ +  - ][ +  - ]:          1 :         MINFO( src_user << " created room id " << room_id );
         [ +  - ][ +  - ]
         [ +  - ][ +  - ]
     800         [ +  - ]:          2 :         g_mtxclient->invite_user( room_id,
     801 [ +  - ][ +  - ]:          2 :           '@' + target_user + ':' + g_mtxclient->server(),
         [ +  - ][ +  - ]
     802 [ +  - ][ +  - ]:          2 :           [room_id](const mtx::responses::Empty&, mtx::http::RequestErr) {} );
                 [ +  - ]
     803         [ +  - ]:          3 :     });
     804                 :          1 : }
     805                 :            : 
     806                 :            : void
     807                 :          1 : piac::matrix_message( const std::string& src_user,
     808                 :            :                       const std::string& target_user,
     809                 :            :                       const std::string& msg )
     810                 :            : // *****************************************************************************
     811                 :            : //  Send a message to a user
     812                 :            : // *****************************************************************************
     813                 :            : {
     814 [ +  - ][ +  - ]:          1 :   MINFO( src_user << " messages " << target_user );
         [ +  - ][ +  - ]
                 [ +  - ]
     815                 :          1 :   invite_room( src_user, target_user, msg );
     816                 :          1 : }
     817                 :            : 
     818                 :            : static void
     819                 :          0 : send_encrypted_reply( const std::string& room_id, const std::string& reply_msg )
     820                 :            : // *****************************************************************************
     821                 :            : // *****************************************************************************
     822                 :            : {
     823 [ -  - ][ -  - ]:          0 :   MINFO( "sending encrypted reply" );
                 [ -  - ]
     824                 :            : 
     825                 :            :   // Create a megolm session if it doesn't already exist
     826         [ -  - ]:          0 :   if (g_storage.outbound_group_exists( room_id )) {
     827         [ -  - ]:          0 :     auto session_obj = g_storage.get_outbound_group_session( room_id );
     828         [ -  - ]:          0 :     send_group_message( session_obj.session,
     829                 :            :                         session_obj.data.session_id,
     830                 :            :                         room_id,
     831                 :            :                         reply_msg );
     832                 :            : 
     833                 :            :   } else {
     834 [ -  - ][ -  - ]:          0 :     MINFO( "creating new megolm outbound session" );
                 [ -  - ]
     835                 :          0 :     create_outbound_megolm_session( room_id, reply_msg );
     836                 :            :   }
     837                 :          0 : }
     838                 :            : 
     839                 :            : static void
     840                 :         13 : parse_messages( const mtx::responses::Sync& res )
     841                 :            : // *****************************************************************************
     842                 :            : //! Parse received messages after sync
     843                 :            : //! \param[in] res Sync response to parse
     844                 :            : // *****************************************************************************
     845                 :            : {
     846 [ +  - ][ +  - ]:         13 :   MDEBUG( "parse_messages" );
         [ +  - ][ +  - ]
     847                 :            : 
     848         [ +  + ]:         14 :   for (const auto& room : res.rooms.invite) {
     849         [ +  - ]:          1 :     auto room_id = room.first;
     850                 :            : 
     851 [ +  - ][ +  - ]:          1 :     MINFO( "joining room " + room_id );
         [ +  - ][ +  - ]
                 [ +  - ]
     852         [ +  - ]:          2 :     g_mtxclient->join_room( room_id,
     853                 :          1 :       [room_id](const mtx::responses::RoomId&, mtx::http::RequestErr e) {
     854         [ -  + ]:          1 :         if (e) {
     855                 :          0 :           print_errors( e );
     856 [ -  - ][ -  - ]:          0 :           MERROR( "failed to join room " << room_id );
         [ -  - ][ -  - ]
     857                 :          0 :           return;
     858                 :            :         }
     859 [ +  - ][ +  - ]:          2 :     } );
     860                 :            :   }
     861                 :            : 
     862                 :            :   // Check if we have any new m.room_key messages
     863                 :            :   // (i.e starting a new megolm session)
     864         [ +  - ]:         13 :   handle_to_device_msgs( res.to_device );
     865                 :            : 
     866                 :            :   // Check if the uploaded one time keys are enough
     867         [ +  + ]:         26 :   for (const auto &device : res.device_one_time_keys_count) {
     868         [ -  + ]:         13 :     if (device.second < 50) {
     869 [ -  - ][ -  - ]:          0 :       MDEBUG( "number of one time keys: " << device.second );
         [ -  - ][ -  - ]
                 [ -  - ]
     870         [ -  - ]:          0 :       g_olmclient->generate_one_time_keys( 50 - device.second );
     871 [ -  - ][ -  - ]:          0 :       g_mtxclient->upload_keys( g_olmclient->create_upload_keys_request(),
     872         [ -  - ]:          0 :                                 &keys_uploaded_cb );
     873                 :            :     }
     874                 :            :   }
     875                 :            : 
     876         [ +  + ]:         20 :   for (const auto &room : res.rooms.join) {
     877                 :            : 
     878         [ +  - ]:         14 :     const std::string room_id = room.first;
     879                 :            : 
     880         [ +  + ]:          8 :     for (const auto &e : room.second.state.events) {
     881         [ -  + ]:          1 :       if (is_room_encryption( e )) {
     882 [ -  - ][ -  - ]:          0 :         mark_encrypted_room( RoomId(room_id) );
     883 [ -  - ][ -  - ]:          0 :         MDEBUG( "room state events " << get_json(e) );
         [ -  - ][ -  - ]
         [ -  - ][ -  - ]
     884         [ -  + ]:          1 :       } else if (is_member_event( e )) {
     885                 :            :         using namespace mtx::events;
     886 [ -  - ][ -  - ]:          0 :         auto m = std::get< StateEvent< state::Member > >( e );
     887 [ -  - ][ -  - ]:          0 :         get_device_keys( UserId( m.state_key ) );
     888         [ -  - ]:          0 :         g_storage.add_member( room_id, m.state_key );
     889                 :            :       }
     890                 :            :     }
     891                 :            : 
     892         [ +  + ]:         37 :     for (const auto &e : room.second.timeline.events) {
     893         [ -  + ]:         30 :       if (is_room_encryption(e)) {
     894                 :            : 
     895 [ -  - ][ -  - ]:          0 :         mark_encrypted_room( RoomId( room_id ) );
     896 [ -  - ][ -  - ]:          0 :         MDEBUG( "room timeline events " << get_json(e) );
         [ -  - ][ -  - ]
         [ -  - ][ -  - ]
     897                 :            : 
     898         [ +  + ]:         30 :       } else if (is_member_event( e )) {
     899                 :            : 
     900                 :            :         using namespace mtx::events;
     901 [ +  - ][ +  - ]:         18 :         auto m = std::get< StateEvent< state::Member > >( e );
     902 [ +  - ][ +  - ]:          9 :         get_device_keys( UserId( m.state_key ) );
     903         [ +  - ]:          9 :         g_storage.add_member( room_id, m.state_key );
     904                 :            : 
     905         [ -  + ]:         21 :       } else if (is_encrypted( e )) {
     906                 :            : 
     907 [ -  - ][ -  - ]:          0 :         MDEBUG( "received an encrypted event: " << room_id );
         [ -  - ][ -  - ]
                 [ -  - ]
     908 [ -  - ][ -  - ]:          0 :         MDEBUG( get_json(e) );
         [ -  - ][ -  - ]
                 [ -  - ]
     909                 :            : 
     910                 :            :         using mtx::events::EncryptedEvent;
     911                 :            :         using mtx::events::msg::Encrypted;
     912 [ -  - ][ -  - ]:          0 :         auto msg = std::get< EncryptedEvent< Encrypted > >( e );
     913                 :            : 
     914 [ -  - ][ -  - ]:          0 :         if (g_storage.inbound_group_exists(
     915                 :            :               room_id, msg.content.session_id, msg.content.sender_key))
     916                 :            :         {
     917                 :            :           auto r = g_olmclient->decrypt_group_message(
     918                 :            :             g_storage.get_inbound_group_session(
     919                 :            :               room_id, msg.content.session_id, msg.content.sender_key ),
     920 [ -  - ][ -  - ]:          0 :             msg.content.ciphertext );
     921                 :            : 
     922         [ -  - ]:          0 :           auto msg_str = std::string( begin(r.data), end(r.data) );
     923         [ -  - ]:          0 :           const auto body = nlohmann::json::parse(msg_str).
     924 [ -  - ][ -  - ]:          0 :             at("content").at("body").get< std::string >();
         [ -  - ][ -  - ]
                 [ -  - ]
     925                 :            : 
     926 [ -  - ][ -  - ]:          0 :           MDEBUG( "decrypted data: " << body );
         [ -  - ][ -  - ]
                 [ -  - ]
     927 [ -  - ][ -  - ]:          0 :           MDEBUG( "decrypted message index: " << r.message_index );
         [ -  - ][ -  - ]
                 [ -  - ]
     928                 :            : 
     929 [ -  - ][ -  - ]:          0 :           if (msg.sender != g_mtxclient->user_id().to_string()) {
                 [ -  - ]
     930                 :            :             // Send a reply back to the sender
     931 [ -  - ][ -  - ]:          0 :             std::string reply_txt( msg.sender + ": you said '" + body + "'");
                 [ -  - ]
     932         [ -  - ]:          0 :             send_encrypted_reply( room_id, reply_txt );
     933                 :            :           }
     934                 :            : 
     935                 :            :         } else {
     936 [ -  - ][ -  - ]:          0 :           MWARNING( "no megolm session found to decrypt the event" );
         [ -  - ][ -  - ]
     937                 :            :         }
     938                 :            : 
     939                 :            :       }
     940                 :            :     }
     941                 :            : 
     942                 :            :   }
     943                 :            : 
     944                 :            :   // tell the message thread that we have just parsed messages
     945         [ +  - ]:         26 :   zmqpp::message msg;
     946         [ +  - ]:         13 :   msg << "parsed_messages";
     947         [ +  - ]:         13 :   g_msg_mtx.back().send( msg );
     948                 :         13 : }
     949                 :            : 
     950                 :            : static void
     951                 :         13 : sync_handler( const mtx::responses::Sync& res, mtx::http::RequestErr err )
     952                 :            : // *****************************************************************************
     953                 :            : // Callback to executed after a /sync request completes.
     954                 :            : //! \param[in] res Sync response
     955                 :            : //! \param[in] err Errors if any
     956                 :            : // *****************************************************************************
     957                 :            : {
     958 [ +  - ][ +  - ]:         13 :   MDEBUG( "sync_handler" );
         [ +  - ][ +  - ]
     959                 :            : 
     960                 :         13 :   mtx::http::SyncOpts opts;
     961                 :            : 
     962         [ -  + ]:         13 :   if (err) {
     963 [ -  - ][ -  - ]:          0 :     MERROR( "error during sync" );
         [ -  - ][ -  - ]
     964         [ -  - ]:          0 :     print_errors( err );
     965         [ -  - ]:          0 :     opts.since = g_mtxclient->next_batch_token();
     966 [ -  - ][ -  - ]:          0 :     g_mtxclient->sync( opts, &sync_handler );
     967                 :          0 :     return;
     968                 :            :   }
     969                 :            : 
     970         [ +  + ]:         13 :   if (piac::g_matrix_shutdown) return;
     971                 :            : 
     972         [ +  - ]:         10 :   parse_messages( res );
     973                 :         10 :   opts.timeout = piac::g_matrix_sync_timeout;
     974         [ +  - ]:         10 :   opts.since = res.next_batch;
     975         [ +  - ]:         10 :   g_mtxclient->set_next_batch_token( res.next_batch );
     976 [ +  - ][ +  - ]:         10 :   g_mtxclient->sync( opts, &sync_handler );
     977                 :            : }
     978                 :            : 
     979                 :            : static void
     980                 :          3 : initial_sync_handler( const mtx::responses::Sync& res,
     981                 :            :                       mtx::http::RequestErr err )
     982                 :            : // *****************************************************************************
     983                 :            : // Callback execute after the first (initial) /sync request completes
     984                 :            : //! \param[in] res Sync response
     985                 :            : //! \param[in] err Errors if any
     986                 :            : // *****************************************************************************
     987                 :            : {
     988 [ +  - ][ +  - ]:          3 :   MDEBUG( "initial_sync_handler" );
         [ +  - ][ +  - ]
     989                 :            : 
     990                 :          3 :   mtx::http::SyncOpts opts;
     991                 :            : 
     992         [ -  + ]:          3 :   if (err) {
     993 [ -  - ][ -  - ]:          0 :     MERROR( "error during initial sync" );
         [ -  - ][ -  - ]
     994         [ -  - ]:          0 :     print_errors( err );
     995                 :            : 
     996         [ -  - ]:          0 :     if (err->status_code != 200) {
     997 [ -  - ][ -  - ]:          0 :       MERROR( "retrying initial sync ..." );
         [ -  - ][ -  - ]
     998                 :          0 :       opts.timeout = 0;
     999 [ -  - ][ -  - ]:          0 :       g_mtxclient->sync( opts, &initial_sync_handler );
    1000                 :            :     }
    1001                 :            : 
    1002                 :          0 :     return;
    1003                 :            :   }
    1004                 :            : 
    1005         [ -  + ]:          3 :   if (piac::g_matrix_shutdown) return;
    1006                 :            : 
    1007         [ +  - ]:          3 :   parse_messages( res );
    1008                 :            : 
    1009         [ +  + ]:          4 :   for (const auto &room : res.rooms.join) {
    1010         [ +  - ]:          2 :     const auto room_id = room.first;
    1011         [ -  + ]:          1 :     for (const auto &e : room.second.state.events) {
    1012         [ -  - ]:          0 :       if (is_member_event(e)) {
    1013                 :            :         using namespace mtx::events;
    1014 [ -  - ][ -  - ]:          0 :         auto m = std::get< StateEvent< state::Member > >( e );
    1015 [ -  - ][ -  - ]:          0 :         get_device_keys( UserId( m.state_key ) );
    1016         [ -  - ]:          0 :         g_storage.add_member( room_id, m.state_key );
    1017                 :            :       }
    1018                 :            :     }
    1019                 :            :   }
    1020                 :            : 
    1021         [ +  - ]:          3 :   opts.since = res.next_batch;
    1022                 :          3 :   opts.timeout = piac::g_matrix_sync_timeout;
    1023         [ +  - ]:          3 :   g_mtxclient->set_next_batch_token( res.next_batch );
    1024 [ +  - ][ +  - ]:          3 :   g_mtxclient->sync( opts, &sync_handler );
    1025                 :            : }
    1026                 :            : 
    1027                 :            : static void
    1028                 :          3 : login_cb( const mtx::responses::Login&, mtx::http::RequestErr error )
    1029                 :            : // *****************************************************************************
    1030                 :            : //! Callback executed after login attempt
    1031                 :            : //! \param[in] error Error if any
    1032                 :            : // *****************************************************************************
    1033                 :            : {
    1034 [ +  - ][ +  - ]:          3 :   MLOG_SET_THREAD_NAME( "mtx" );
    1035 [ +  - ][ +  - ]:          3 :   MDEBUG( "login_cb" );
                 [ +  - ]
    1036                 :            : 
    1037         [ -  + ]:          3 :   if (error) {
    1038 [ -  - ][ -  - ]:          0 :     MERROR( "login error" );
                 [ -  - ]
    1039                 :          0 :     print_errors( error );
    1040         [ -  - ]:          0 :     if (not error->matrix_error.error.empty() ) {
    1041                 :          0 :       std::cerr << error->matrix_error.error << '\n';
    1042                 :            :     }
    1043                 :          0 :     return;
    1044                 :            :   }
    1045                 :            : 
    1046 [ +  - ][ +  - ]:          3 :   MDEBUG( "user id: " + g_mtxclient->user_id().to_string() );
         [ +  - ][ +  - ]
         [ +  - ][ +  - ]
    1047 [ +  - ][ +  - ]:          3 :   MDEBUG( "device id: " + g_mtxclient->device_id() );
         [ +  - ][ +  - ]
                 [ +  - ]
    1048 [ +  - ][ +  - ]:          3 :   MDEBUG( "ed25519: " + g_olmclient->identity_keys().ed25519 );
         [ +  - ][ +  - ]
                 [ +  - ]
    1049 [ +  - ][ +  - ]:          3 :   MDEBUG( "curve25519: " + g_olmclient->identity_keys().curve25519 );
         [ +  - ][ +  - ]
                 [ +  - ]
    1050                 :            : 
    1051                 :            :   // Upload one-time keys
    1052         [ +  - ]:          3 :   g_olmclient->set_user_id( g_mtxclient->user_id().to_string() );
    1053                 :          3 :   g_olmclient->set_device_id( g_mtxclient->device_id() );
    1054                 :          3 :   g_olmclient->generate_one_time_keys( 50 );
    1055                 :            : 
    1056 [ +  - ][ +  - ]:          6 :   g_mtxclient->upload_keys( g_olmclient->create_upload_keys_request(),
    1057                 :          3 :     [](const mtx::responses::UploadKeys&, mtx::http::RequestErr err ) {
    1058         [ -  + ]:          3 :       if (err) {
    1059         [ -  - ]:          0 :         print_errors(err);
    1060                 :          0 :         return;
    1061                 :            :       }
    1062         [ +  - ]:          3 :       g_olmclient->mark_keys_as_published();
    1063 [ +  - ][ +  - ]:          3 :       MDEBUG( "keys uploaded" );
         [ +  - ][ +  - ]
    1064 [ +  - ][ +  - ]:          3 :       MDEBUG( "starting initial sync" );
         [ +  - ][ +  - ]
    1065                 :          3 :       mtx::http::SyncOpts opts;
    1066                 :          3 :       opts.timeout = 0;
    1067 [ +  - ][ +  - ]:          3 :       g_mtxclient->sync( opts, &initial_sync_handler );
    1068                 :          6 :     } );
    1069                 :            : }
    1070                 :            : 
    1071                 :            : void
    1072                 :          3 : piac::matrix_thread( const std::string& server,
    1073                 :            :                      const std::string& username,
    1074                 :            :                      const std::string& password,
    1075                 :            :                      const std::string& db_key )
    1076                 :            : // *****************************************************************************
    1077                 :            : //  Entry point to thread to communicate with a matrix server
    1078                 :            : //! \param[in] server Matrix hostname to connect to as \<host\>[:port]
    1079                 :            : //! \param[in] username Username to use
    1080                 :            : //! \param[in] password Password to use
    1081                 :            : //! \param[in] db_key Database key to use to encrypt session db on disk
    1082                 :            : // *****************************************************************************
    1083                 :            : {
    1084 [ +  - ][ +  - ]:          3 :   MLOG_SET_THREAD_NAME( "mtx" );
    1085 [ +  - ][ +  - ]:          3 :   MINFO( "mtx thread initialized" );
         [ +  - ][ +  - ]
    1086 [ +  - ][ +  - ]:          3 :   MDEBUG( "matrix login request to server: " << server << ", username: " <<
         [ +  - ][ +  - ]
         [ +  - ][ +  - ]
         [ +  - ][ +  - ]
         [ +  - ][ +  - ]
    1087                 :            :           username << ", connected: " << std::boolalpha << g_matrix_connected );
    1088                 :            : 
    1089         [ -  + ]:          3 :   if (g_matrix_connected) {
    1090 [ -  - ][ -  - ]:          0 :     MDEBUG( "already connected" );
         [ -  - ][ -  - ]
    1091                 :          0 :     return;
    1092                 :            :   }
    1093                 :            : 
    1094                 :          3 :   int port = 443;
    1095 [ +  - ][ +  - ]:          6 :   auto [host,prt] = split( server, ":" );
                 [ +  - ]
    1096         [ -  + ]:          3 :   if (prt.empty()) {
    1097         [ -  - ]:          0 :     g_mtxclient = std::make_shared< mtx::http::Client >( host );
    1098                 :            :   } else {
    1099                 :            :     try {
    1100         [ +  - ]:          3 :       port = std::stoi( prt );
    1101         [ -  - ]:          0 :     } catch (std::exception&) {
    1102         [ -  - ]:          0 :       std::cerr << "invalid <host>:<port> format\n";
    1103                 :          0 :       return;
    1104                 :            :     }
    1105         [ +  - ]:          3 :     g_mtxclient = std::make_shared< mtx::http::Client >( host, port );
    1106                 :            :   }
    1107 [ +  - ][ +  - ]:          3 :   MDEBUG( "will connect as @" << username << ':' << host << ':' << port );
         [ +  - ][ +  - ]
         [ +  - ][ +  - ]
         [ +  - ][ +  - ]
                 [ +  - ]
    1108                 :            : 
    1109         [ +  - ]:          3 :   g_mtxclient->verify_certificates( false );
    1110         [ +  - ]:          3 :   g_olmclient = std::make_shared< mtx::crypto::OlmClient >();
    1111                 :            : 
    1112 [ +  - ][ +  - ]:          6 :   std::string db_base_filename = "piac-matrix-@" + username + ':' + server;
                 [ +  - ]
    1113         [ +  - ]:          3 :   g_mtx_account_db_filename = db_base_filename + ".account.json";
    1114         [ +  - ]:          3 :   g_mtx_session_db_filename = db_base_filename + ".session.json";
    1115         [ +  - ]:          3 :   g_mtx_db_storage_key = db_key;
    1116                 :            : 
    1117         [ +  - ]:          3 :   std::ifstream db( g_mtx_account_db_filename );
    1118                 :          3 :   std::string db_data( (std::istreambuf_iterator< char >( db )),
    1119         [ +  - ]:          6 :                        std::istreambuf_iterator< char >() );
    1120         [ -  + ]:          3 :   if (db_data.empty()) {
    1121         [ -  - ]:          0 :     g_olmclient->create_new_account();
    1122                 :            :   } else {
    1123         [ +  - ]:          6 :     g_olmclient->load(
    1124 [ +  - ][ +  - ]:          6 :       nlohmann::json::parse(db_data).at("account").get<std::string>(),
         [ +  - ][ +  - ]
    1125                 :            :       g_mtx_db_storage_key );
    1126                 :            :   }
    1127         [ +  - ]:          3 :   g_storage.load();
    1128                 :            : 
    1129                 :            :   // create socket to forward matrix messages to
    1130         [ +  - ]:          3 :   g_msg_mtx.emplace_back( piac::g_ctx_msg, zmqpp::socket_type::pair );
    1131 [ +  - ][ +  - ]:          3 :   g_msg_mtx.back().connect( "inproc://msg_mtx" );
    1132 [ +  - ][ +  - ]:          3 :   MDEBUG( "Connected to inproc:://msg_mtx" );
         [ +  - ][ +  - ]
    1133                 :            : 
    1134                 :          3 :   g_matrix_connected = true;
    1135 [ +  - ][ +  - ]:          3 :   g_mtxclient->login( username, password, login_cb );   // blocking, syncing...
    1136         [ +  - ]:          3 :   g_mtxclient->close();
    1137                 :          3 :   g_matrix_connected = false;
    1138                 :          3 :   g_matrix_shutdown = false;
    1139                 :            : 
    1140                 :            :   // tell the message thread that we are shutting down
    1141         [ +  - ]:          3 :   zmqpp::message msg;
    1142         [ +  - ]:          3 :   msg << "SHUTDOWN";
    1143         [ +  - ]:          3 :   g_msg_mtx.back().send( msg );
    1144                 :          3 :   g_msg_mtx.clear();
    1145                 :            : 
    1146 [ +  - ][ +  - ]:          3 :   MDEBUG( "saving matrix session to " +
         [ +  - ][ +  - ]
         [ +  - ][ +  - ]
                 [ +  - ]
    1147                 :            :           g_mtx_account_db_filename + " and " + g_mtx_session_db_filename );
    1148         [ +  - ]:          3 :   g_storage.save();
    1149         [ +  - ]:          3 :   std::ofstream odb( g_mtx_account_db_filename );
    1150 [ +  - ][ -  + ]:          3 :   if (not odb.is_open()) {
    1151 [ -  - ][ -  - ]:          0 :     MERROR( "Couldn't open matrix account db " << g_mtx_account_db_filename );
         [ -  - ][ -  - ]
                 [ -  - ]
    1152                 :          0 :     return;
    1153                 :            :   }
    1154                 :          6 :   nlohmann::json data;
    1155 [ +  - ][ +  - ]:          3 :   data[ "account" ] = g_olmclient->save( g_mtx_db_storage_key );
                 [ +  - ]
    1156 [ +  - ][ +  - ]:          3 :   odb << data.dump( 2 );
    1157         [ +  - ]:          3 :   odb.close();
    1158                 :            : 
    1159 [ +  - ][ +  - ]:          3 :   MINFO( "mtx thread quit" );
         [ +  - ][ +  - ]
    1160                 :            : }

Generated by: LCOV version 1.14