// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later

#include "DeviceVerificationFlow.h"

#include "Cache.h"
#include "Cache_p.h"
#include "ChatPage.h"
#include "Logging.h"
#include "Utils.h"
#include "timeline/TimelineModel.h"

#include <QDateTime>
#include <QTimer>
#include <iostream>

static constexpr int TIMEOUT = 2 * 60 * 1000; // 2 minutes

namespace msgs = mtx::events::msg;

static mtx::events::msg::KeyVerificationMac
key_verification_mac(mtx::crypto::SAS *sas,
                     mtx::identifiers::User sender,
                     const std::string &senderDevice,
                     mtx::identifiers::User receiver,
                     const std::string &receiverDevice,
                     const std::string &transactionId,
                     std::map<std::string, std::string> keys);

DeviceVerificationFlow::DeviceVerificationFlow(QObject *,
                                               DeviceVerificationFlow::Type flow_type,
                                               TimelineModel *model,
                                               QString userID,
                                               QString deviceId_)
  : sender(false)
  , type(flow_type)
  , deviceId(deviceId_)
  , model_(model)
{
        timeout = new QTimer(this);
        timeout->setSingleShot(true);
        this->sas           = olm::client()->sas_init();
        this->isMacVerified = false;

        auto user_id   = userID.toStdString();
        this->toClient = mtx::identifiers::parse<mtx::identifiers::User>(user_id);
        cache::client()->query_keys(
          user_id, [user_id, this](const UserKeyCache &res, mtx::http::RequestErr err) {
                  if (err) {
                          nhlog::net()->warn("failed to query device keys: {},{}",
                                             mtx::errors::to_string(err->matrix_error.errcode),
                                             static_cast<int>(err->status_code));
                          return;
                  }

                  if (!this->deviceId.isEmpty() &&
                      (res.device_keys.find(deviceId.toStdString()) == res.device_keys.end())) {
                          nhlog::net()->warn("no devices retrieved {}", user_id);
                          return;
                  }

                  this->their_keys = res;
          });

        cache::client()->query_keys(
          http::client()->user_id().to_string(),
          [this](const UserKeyCache &res, mtx::http::RequestErr err) {
                  if (err) {
                          nhlog::net()->warn("failed to query device keys: {},{}",
                                             mtx::errors::to_string(err->matrix_error.errcode),
                                             static_cast<int>(err->status_code));
                          return;
                  }

                  if (res.master_keys.keys.empty())
                          return;

                  if (auto status =
                        cache::verificationStatus(http::client()->user_id().to_string());
                      status && status->user_verified == crypto::Trust::Verified)
                          this->our_trusted_master_key = res.master_keys.keys.begin()->second;
          });

        if (model) {
                connect(this->model_,
                        &TimelineModel::updateFlowEventId,
                        this,
                        [this](std::string event_id_) {
                                this->relation.rel_type = mtx::common::RelationType::Reference;
                                this->relation.event_id = event_id_;
                                this->transaction_id    = event_id_;
                        });
        }

        connect(timeout, &QTimer::timeout, this, [this]() {
                nhlog::crypto()->info("verification: timeout");
                if (state_ != Success && state_ != Failed)
                        this->cancelVerification(DeviceVerificationFlow::Error::Timeout);
        });

        connect(ChatPage::instance(),
                &ChatPage::receivedDeviceVerificationStart,
                this,
                &DeviceVerificationFlow::handleStartMessage);
        connect(ChatPage::instance(),
                &ChatPage::receivedDeviceVerificationAccept,
                this,
                [this](const mtx::events::msg::KeyVerificationAccept &msg) {
                        nhlog::crypto()->info("verification: received accept");
                        if (msg.transaction_id.has_value()) {
                                if (msg.transaction_id.value() != this->transaction_id)
                                        return;
                        } else if (msg.relations.references()) {
                                if (msg.relations.references() != this->relation.event_id)
                                        return;
                        }
                        if ((msg.key_agreement_protocol == "curve25519-hkdf-sha256") &&
                            (msg.hash == "sha256") &&
                            (msg.message_authentication_code == "hkdf-hmac-sha256")) {
                                this->commitment = msg.commitment;
                                if (std::find(msg.short_authentication_string.begin(),
                                              msg.short_authentication_string.end(),
                                              mtx::events::msg::SASMethods::Emoji) !=
                                    msg.short_authentication_string.end()) {
                                        this->method = mtx::events::msg::SASMethods::Emoji;
                                } else {
                                        this->method = mtx::events::msg::SASMethods::Decimal;
                                }
                                this->mac_method = msg.message_authentication_code;
                                this->sendVerificationKey();
                        } else {
                                this->cancelVerification(
                                  DeviceVerificationFlow::Error::UnknownMethod);
                        }
                });

        connect(ChatPage::instance(),
                &ChatPage::receivedDeviceVerificationCancel,
                this,
                [this](const mtx::events::msg::KeyVerificationCancel &msg) {
                        nhlog::crypto()->info("verification: received cancel");
                        if (msg.transaction_id.has_value()) {
                                if (msg.transaction_id.value() != this->transaction_id)
                                        return;
                        } else if (msg.relations.references()) {
                                if (msg.relations.references() != this->relation.event_id)
                                        return;
                        }
                        error_ = User;
                        emit errorChanged();
                        setState(Failed);
                });

        connect(ChatPage::instance(),
                &ChatPage::receivedDeviceVerificationKey,
                this,
                [this](const mtx::events::msg::KeyVerificationKey &msg) {
                        nhlog::crypto()->info("verification: received key");
                        if (msg.transaction_id.has_value()) {
                                if (msg.transaction_id.value() != this->transaction_id)
                                        return;
                        } else if (msg.relations.references()) {
                                if (msg.relations.references() != this->relation.event_id)
                                        return;
                        }

                        if (sender) {
                                if (state_ != WaitingForOtherToAccept) {
                                        this->cancelVerification(OutOfOrder);
                                        return;
                                }
                        } else {
                                if (state_ != WaitingForKeys) {
                                        this->cancelVerification(OutOfOrder);
                                        return;
                                }
                        }

                        this->sas->set_their_key(msg.key);
                        std::string info;
                        if (this->sender == true) {
                                info = "MATRIX_KEY_VERIFICATION_SAS|" +
                                       http::client()->user_id().to_string() + "|" +
                                       http::client()->device_id() + "|" + this->sas->public_key() +
                                       "|" + this->toClient.to_string() + "|" +
                                       this->deviceId.toStdString() + "|" + msg.key + "|" +
                                       this->transaction_id;
                        } else {
                                info = "MATRIX_KEY_VERIFICATION_SAS|" + this->toClient.to_string() +
                                       "|" + this->deviceId.toStdString() + "|" + msg.key + "|" +
                                       http::client()->user_id().to_string() + "|" +
                                       http::client()->device_id() + "|" + this->sas->public_key() +
                                       "|" + this->transaction_id;
                        }

                        nhlog::ui()->info("Info is: '{}'", info);

                        if (this->sender == false) {
                                this->sendVerificationKey();
                        } else {
                                if (this->commitment !=
                                    mtx::crypto::bin2base64_unpadded(
                                      mtx::crypto::sha256(msg.key + this->canonical_json.dump()))) {
                                        this->cancelVerification(
                                          DeviceVerificationFlow::Error::MismatchedCommitment);
                                        return;
                                }
                        }

                        if (this->method == mtx::events::msg::SASMethods::Emoji) {
                                this->sasList = this->sas->generate_bytes_emoji(info);
                                setState(CompareEmoji);
                        } else if (this->method == mtx::events::msg::SASMethods::Decimal) {
                                this->sasList = this->sas->generate_bytes_decimal(info);
                                setState(CompareNumber);
                        }
                });

        connect(
          ChatPage::instance(),
          &ChatPage::receivedDeviceVerificationMac,
          this,
          [this](const mtx::events::msg::KeyVerificationMac &msg) {
                  nhlog::crypto()->info("verification: received mac");
                  if (msg.transaction_id.has_value()) {
                          if (msg.transaction_id.value() != this->transaction_id)
                                  return;
                  } else if (msg.relations.references()) {
                          if (msg.relations.references() != this->relation.event_id)
                                  return;
                  }

                  std::map<std::string, std::string> key_list;
                  std::string key_string;
                  for (const auto &mac : msg.mac) {
                          for (const auto &[deviceid, key] : their_keys.device_keys) {
                                  (void)deviceid;
                                  if (key.keys.count(mac.first))
                                          key_list[mac.first] = key.keys.at(mac.first);
                          }

                          if (their_keys.master_keys.keys.count(mac.first))
                                  key_list[mac.first] = their_keys.master_keys.keys[mac.first];
                          if (their_keys.user_signing_keys.keys.count(mac.first))
                                  key_list[mac.first] =
                                    their_keys.user_signing_keys.keys[mac.first];
                          if (their_keys.self_signing_keys.keys.count(mac.first))
                                  key_list[mac.first] =
                                    their_keys.self_signing_keys.keys[mac.first];
                  }
                  auto macs = key_verification_mac(sas.get(),
                                                   toClient,
                                                   this->deviceId.toStdString(),
                                                   http::client()->user_id(),
                                                   http::client()->device_id(),
                                                   this->transaction_id,
                                                   key_list);

                  for (const auto &[key, mac] : macs.mac) {
                          if (mac != msg.mac.at(key)) {
                                  this->cancelVerification(
                                    DeviceVerificationFlow::Error::KeyMismatch);
                                  return;
                          }
                  }

                  if (msg.keys == macs.keys) {
                          mtx::requests::KeySignaturesUpload req;
                          if (utils::localUser().toStdString() == this->toClient.to_string()) {
                                  // self verification, sign master key with device key, if we
                                  // verified it
                                  for (const auto &mac : msg.mac) {
                                          if (their_keys.master_keys.keys.count(mac.first)) {
                                                  json j = their_keys.master_keys;
                                                  j.erase("signatures");
                                                  j.erase("unsigned");
                                                  mtx::crypto::CrossSigningKeys master_key = j;
                                                  master_key
                                                    .signatures[utils::localUser().toStdString()]
                                                               ["ed25519:" +
                                                                http::client()->device_id()] =
                                                    olm::client()->sign_message(j.dump());
                                                  req.signatures[utils::localUser().toStdString()]
                                                                [master_key.keys.at(mac.first)] =
                                                    master_key;
                                          } else if (mac.first ==
                                                     "ed25519:" + this->deviceId.toStdString()) {
                                                  // Sign their device key with self signing key

                                                  auto device_id = this->deviceId.toStdString();

                                                  if (their_keys.device_keys.count(device_id)) {
                                                          json j =
                                                            their_keys.device_keys.at(device_id);
                                                          j.erase("signatures");
                                                          j.erase("unsigned");

                                                          auto secret = cache::secret(
                                                            mtx::secret_storage::secrets::
                                                              cross_signing_self_signing);
                                                          if (!secret)
                                                                  continue;
                                                          auto ssk =
                                                            mtx::crypto::PkSigning::from_seed(
                                                              *secret);

                                                          mtx::crypto::DeviceKeys dev = j;
                                                          dev.signatures
                                                            [utils::localUser().toStdString()]
                                                            ["ed25519:" + ssk.public_key()] =
                                                            ssk.sign(j.dump());

                                                          req.signatures[utils::localUser()
                                                                           .toStdString()]
                                                                        [device_id] = dev;
                                                  }
                                          }
                                  }
                          } else {
                                  // Sign their master key with user signing key
                                  for (const auto &mac : msg.mac) {
                                          if (their_keys.master_keys.keys.count(mac.first)) {
                                                  json j = their_keys.master_keys;
                                                  j.erase("signatures");
                                                  j.erase("unsigned");

                                                  auto secret =
                                                    cache::secret(mtx::secret_storage::secrets::
                                                                    cross_signing_user_signing);
                                                  if (!secret)
                                                          continue;
                                                  auto usk =
                                                    mtx::crypto::PkSigning::from_seed(*secret);

                                                  mtx::crypto::CrossSigningKeys master_key = j;
                                                  master_key
                                                    .signatures[utils::localUser().toStdString()]
                                                               ["ed25519:" + usk.public_key()] =
                                                    usk.sign(j.dump());

                                                  req.signatures[toClient.to_string()]
                                                                [master_key.keys.at(mac.first)] =
                                                    master_key;
                                          }
                                  }
                          }

                          if (!req.signatures.empty()) {
                                  http::client()->keys_signatures_upload(
                                    req,
                                    [](const mtx::responses::KeySignaturesUpload &res,
                                       mtx::http::RequestErr err) {
                                            if (err) {
                                                    nhlog::net()->error(
                                                      "failed to upload signatures: {},{}",
                                                      mtx::errors::to_string(
                                                        err->matrix_error.errcode),
                                                      static_cast<int>(err->status_code));
                                            }

                                            for (const auto &[user_id, tmp] : res.errors)
                                                    for (const auto &[key_id, e] : tmp)
                                                            nhlog::net()->error(
                                                              "signature error for user {} and key "
                                                              "id {}: {}, {}",
                                                              user_id,
                                                              key_id,
                                                              mtx::errors::to_string(e.errcode),
                                                              e.error);
                                    });
                          }

                          this->isMacVerified = true;
                          this->acceptDevice();
                  } else {
                          this->cancelVerification(DeviceVerificationFlow::Error::KeyMismatch);
                  }
          });

        connect(ChatPage::instance(),
                &ChatPage::receivedDeviceVerificationReady,
                this,
                [this](const mtx::events::msg::KeyVerificationReady &msg) {
                        nhlog::crypto()->info("verification: received ready");
                        if (!sender) {
                                if (msg.from_device != http::client()->device_id()) {
                                        error_ = User;
                                        emit errorChanged();
                                        setState(Failed);
                                }

                                return;
                        }

                        if (msg.transaction_id.has_value()) {
                                if (msg.transaction_id.value() != this->transaction_id)
                                        return;
                        } else if (msg.relations.references()) {
                                if (msg.relations.references() != this->relation.event_id)
                                        return;
                                else {
                                        this->deviceId = QString::fromStdString(msg.from_device);
                                }
                        }
                        this->startVerificationRequest();
                });

        connect(ChatPage::instance(),
                &ChatPage::receivedDeviceVerificationDone,
                this,
                [this](const mtx::events::msg::KeyVerificationDone &msg) {
                        nhlog::crypto()->info("verification: receoved done");
                        if (msg.transaction_id.has_value()) {
                                if (msg.transaction_id.value() != this->transaction_id)
                                        return;
                        } else if (msg.relations.references()) {
                                if (msg.relations.references() != this->relation.event_id)
                                        return;
                        }
                        nhlog::ui()->info("Flow done on other side");
                });

        timeout->start(TIMEOUT);
}

QString
DeviceVerificationFlow::state()
{
        switch (state_) {
        case PromptStartVerification:
                return "PromptStartVerification";
        case CompareEmoji:
                return "CompareEmoji";
        case CompareNumber:
                return "CompareNumber";
        case WaitingForKeys:
                return "WaitingForKeys";
        case WaitingForOtherToAccept:
                return "WaitingForOtherToAccept";
        case WaitingForMac:
                return "WaitingForMac";
        case Success:
                return "Success";
        case Failed:
                return "Failed";
        default:
                return "";
        }
}

void
DeviceVerificationFlow::next()
{
        if (sender) {
                switch (state_) {
                case PromptStartVerification:
                        sendVerificationRequest();
                        break;
                case CompareEmoji:
                case CompareNumber:
                        sendVerificationMac();
                        break;
                case WaitingForKeys:
                case WaitingForOtherToAccept:
                case WaitingForMac:
                case Success:
                case Failed:
                        nhlog::db()->error("verification: Invalid state transition!");
                        break;
                }
        } else {
                switch (state_) {
                case PromptStartVerification:
                        if (canonical_json.is_null())
                                sendVerificationReady();
                        else // legacy path without request and ready
                                acceptVerificationRequest();
                        break;
                case CompareEmoji:
                        [[fallthrough]];
                case CompareNumber:
                        sendVerificationMac();
                        break;
                case WaitingForKeys:
                case WaitingForOtherToAccept:
                case WaitingForMac:
                case Success:
                case Failed:
                        nhlog::db()->error("verification: Invalid state transition!");
                        break;
                }
        }
}

QString
DeviceVerificationFlow::getUserId()
{
        return QString::fromStdString(this->toClient.to_string());
}

QString
DeviceVerificationFlow::getDeviceId()
{
        return this->deviceId;
}

bool
DeviceVerificationFlow::getSender()
{
        return this->sender;
}

std::vector<int>
DeviceVerificationFlow::getSasList()
{
        return this->sasList;
}

bool
DeviceVerificationFlow::isSelfVerification() const
{
        return this->toClient.to_string() == http::client()->user_id().to_string();
}

void
DeviceVerificationFlow::setEventId(std::string event_id_)
{
        this->relation.rel_type = mtx::common::RelationType::Reference;
        this->relation.event_id = event_id_;
        this->transaction_id    = event_id_;
}

void
DeviceVerificationFlow::handleStartMessage(const mtx::events::msg::KeyVerificationStart &msg,
                                           std::string)
{
        if (msg.transaction_id.has_value()) {
                if (msg.transaction_id.value() != this->transaction_id)
                        return;
        } else if (msg.relations.references()) {
                if (msg.relations.references() != this->relation.event_id)
                        return;
        }
        if ((std::find(msg.key_agreement_protocols.begin(),
                       msg.key_agreement_protocols.end(),
                       "curve25519-hkdf-sha256") != msg.key_agreement_protocols.end()) &&
            (std::find(msg.hashes.begin(), msg.hashes.end(), "sha256") != msg.hashes.end()) &&
            (std::find(msg.message_authentication_codes.begin(),
                       msg.message_authentication_codes.end(),
                       "hkdf-hmac-sha256") != msg.message_authentication_codes.end())) {
                if (std::find(msg.short_authentication_string.begin(),
                              msg.short_authentication_string.end(),
                              mtx::events::msg::SASMethods::Emoji) !=
                    msg.short_authentication_string.end()) {
                        this->method = mtx::events::msg::SASMethods::Emoji;
                } else if (std::find(msg.short_authentication_string.begin(),
                                     msg.short_authentication_string.end(),
                                     mtx::events::msg::SASMethods::Decimal) !=
                           msg.short_authentication_string.end()) {
                        this->method = mtx::events::msg::SASMethods::Decimal;
                } else {
                        this->cancelVerification(DeviceVerificationFlow::Error::UnknownMethod);
                        return;
                }
                if (!sender)
                        this->canonical_json = nlohmann::json(msg);
                else {
                        if (utils::localUser().toStdString() < this->toClient.to_string()) {
                                this->canonical_json = nlohmann::json(msg);
                        }
                }

                if (state_ != PromptStartVerification)
                        this->acceptVerificationRequest();
        } else {
                this->cancelVerification(DeviceVerificationFlow::Error::UnknownMethod);
        }
}

//! accepts a verification
void
DeviceVerificationFlow::acceptVerificationRequest()
{
        mtx::events::msg::KeyVerificationAccept req;

        req.method                      = mtx::events::msg::VerificationMethods::SASv1;
        req.key_agreement_protocol      = "curve25519-hkdf-sha256";
        req.hash                        = "sha256";
        req.message_authentication_code = "hkdf-hmac-sha256";
        if (this->method == mtx::events::msg::SASMethods::Emoji)
                req.short_authentication_string = {mtx::events::msg::SASMethods::Emoji};
        else if (this->method == mtx::events::msg::SASMethods::Decimal)
                req.short_authentication_string = {mtx::events::msg::SASMethods::Decimal};
        req.commitment = mtx::crypto::bin2base64_unpadded(
          mtx::crypto::sha256(this->sas->public_key() + this->canonical_json.dump()));

        send(req);
        setState(WaitingForKeys);
}
//! responds verification request
void
DeviceVerificationFlow::sendVerificationReady()
{
        mtx::events::msg::KeyVerificationReady req;

        req.from_device = http::client()->device_id();
        req.methods     = {mtx::events::msg::VerificationMethods::SASv1};

        send(req);
        setState(WaitingForKeys);
}
//! accepts a verification
void
DeviceVerificationFlow::sendVerificationDone()
{
        mtx::events::msg::KeyVerificationDone req;

        send(req);
}
//! starts the verification flow
void
DeviceVerificationFlow::startVerificationRequest()
{
        mtx::events::msg::KeyVerificationStart req;

        req.from_device                  = http::client()->device_id();
        req.method                       = mtx::events::msg::VerificationMethods::SASv1;
        req.key_agreement_protocols      = {"curve25519-hkdf-sha256"};
        req.hashes                       = {"sha256"};
        req.message_authentication_codes = {"hkdf-hmac-sha256"};
        req.short_authentication_string  = {mtx::events::msg::SASMethods::Decimal,
                                           mtx::events::msg::SASMethods::Emoji};

        if (this->type == DeviceVerificationFlow::Type::ToDevice) {
                mtx::requests::ToDeviceMessages<mtx::events::msg::KeyVerificationStart> body;
                req.transaction_id   = this->transaction_id;
                this->canonical_json = nlohmann::json(req);
        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) {
                req.relations.relations.push_back(this->relation);
                // Set synthesized to surpress the nheko relation extensions
                req.relations.synthesized = true;
                this->canonical_json      = nlohmann::json(req);
        }
        send(req);
        setState(WaitingForOtherToAccept);
}
//! sends a verification request
void
DeviceVerificationFlow::sendVerificationRequest()
{
        mtx::events::msg::KeyVerificationRequest req;

        req.from_device = http::client()->device_id();
        req.methods     = {mtx::events::msg::VerificationMethods::SASv1};

        if (this->type == DeviceVerificationFlow::Type::ToDevice) {
                QDateTime currentTime = QDateTime::currentDateTimeUtc();

                req.timestamp = (uint64_t)currentTime.toMSecsSinceEpoch();

        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) {
                req.to      = this->toClient.to_string();
                req.msgtype = "m.key.verification.request";
                req.body = "User is requesting to verify keys with you. However, your client does "
                           "not support this method, so you will need to use the legacy method of "
                           "key verification.";
        }

        send(req);
        setState(WaitingForOtherToAccept);
}
//! cancels a verification flow
void
DeviceVerificationFlow::cancelVerification(DeviceVerificationFlow::Error error_code)
{
        if (state_ == State::Success || state_ == State::Failed)
                return;

        mtx::events::msg::KeyVerificationCancel req;

        if (error_code == DeviceVerificationFlow::Error::UnknownMethod) {
                req.code   = "m.unknown_method";
                req.reason = "unknown method received";
        } else if (error_code == DeviceVerificationFlow::Error::MismatchedCommitment) {
                req.code   = "m.mismatched_commitment";
                req.reason = "commitment didn't match";
        } else if (error_code == DeviceVerificationFlow::Error::MismatchedSAS) {
                req.code   = "m.mismatched_sas";
                req.reason = "sas didn't match";
        } else if (error_code == DeviceVerificationFlow::Error::KeyMismatch) {
                req.code   = "m.key_match";
                req.reason = "keys did not match";
        } else if (error_code == DeviceVerificationFlow::Error::Timeout) {
                req.code   = "m.timeout";
                req.reason = "timed out";
        } else if (error_code == DeviceVerificationFlow::Error::User) {
                req.code   = "m.user";
                req.reason = "user cancelled the verification";
        } else if (error_code == DeviceVerificationFlow::Error::OutOfOrder) {
                req.code   = "m.unexpected_message";
                req.reason = "received messages out of order";
        }

        this->error_ = error_code;
        emit errorChanged();
        this->setState(Failed);

        send(req);
}
//! sends the verification key
void
DeviceVerificationFlow::sendVerificationKey()
{
        mtx::events::msg::KeyVerificationKey req;

        req.key = this->sas->public_key();

        send(req);
}

mtx::events::msg::KeyVerificationMac
key_verification_mac(mtx::crypto::SAS *sas,
                     mtx::identifiers::User sender,
                     const std::string &senderDevice,
                     mtx::identifiers::User receiver,
                     const std::string &receiverDevice,
                     const std::string &transactionId,
                     std::map<std::string, std::string> keys)
{
        mtx::events::msg::KeyVerificationMac req;

        std::string info = "MATRIX_KEY_VERIFICATION_MAC" + sender.to_string() + senderDevice +
                           receiver.to_string() + receiverDevice + transactionId;

        std::string key_list;
        bool first = true;
        for (const auto &[key_id, key] : keys) {
                req.mac[key_id] = sas->calculate_mac(key, info + key_id);

                if (!first)
                        key_list += ",";
                key_list += key_id;
                first = false;
        }

        req.keys = sas->calculate_mac(key_list, info + "KEY_IDS");

        return req;
}

//! sends the mac of the keys
void
DeviceVerificationFlow::sendVerificationMac()
{
        std::map<std::string, std::string> key_list;
        key_list["ed25519:" + http::client()->device_id()] = olm::client()->identity_keys().ed25519;

        // send our master key, if we trust it
        if (!this->our_trusted_master_key.empty())
                key_list["ed25519:" + our_trusted_master_key] = our_trusted_master_key;

        mtx::events::msg::KeyVerificationMac req =
          key_verification_mac(sas.get(),
                               http::client()->user_id(),
                               http::client()->device_id(),
                               this->toClient,
                               this->deviceId.toStdString(),
                               this->transaction_id,
                               key_list);

        send(req);

        setState(WaitingForMac);
        acceptDevice();
}
//! Completes the verification flow
void
DeviceVerificationFlow::acceptDevice()
{
        if (!isMacVerified) {
                setState(WaitingForMac);
        } else if (state_ == WaitingForMac) {
                cache::markDeviceVerified(this->toClient.to_string(), this->deviceId.toStdString());
                this->sendVerificationDone();
                setState(Success);

                // Request secrets. We should probably check somehow, if a device knowns about the
                // secrets.
                if (utils::localUser().toStdString() == this->toClient.to_string() &&
                    (!cache::secret(mtx::secret_storage::secrets::cross_signing_self_signing) ||
                     !cache::secret(mtx::secret_storage::secrets::cross_signing_user_signing))) {
                        olm::request_cross_signing_keys();
                }
        }
}

void
DeviceVerificationFlow::unverify()
{
        cache::markDeviceUnverified(this->toClient.to_string(), this->deviceId.toStdString());

        emit refreshProfile();
}

QSharedPointer<DeviceVerificationFlow>
DeviceVerificationFlow::NewInRoomVerification(QObject *parent_,
                                              TimelineModel *timelineModel_,
                                              const mtx::events::msg::KeyVerificationRequest &msg,
                                              QString other_user_,
                                              QString event_id_)
{
        QSharedPointer<DeviceVerificationFlow> flow(
          new DeviceVerificationFlow(parent_,
                                     Type::RoomMsg,
                                     timelineModel_,
                                     other_user_,
                                     QString::fromStdString(msg.from_device)));

        flow->setEventId(event_id_.toStdString());

        if (std::find(msg.methods.begin(),
                      msg.methods.end(),
                      mtx::events::msg::VerificationMethods::SASv1) == msg.methods.end()) {
                flow->cancelVerification(UnknownMethod);
        }

        return flow;
}
QSharedPointer<DeviceVerificationFlow>
DeviceVerificationFlow::NewToDeviceVerification(QObject *parent_,
                                                const mtx::events::msg::KeyVerificationRequest &msg,
                                                QString other_user_,
                                                QString txn_id_)
{
        QSharedPointer<DeviceVerificationFlow> flow(new DeviceVerificationFlow(
          parent_, Type::ToDevice, nullptr, other_user_, QString::fromStdString(msg.from_device)));
        flow->transaction_id = txn_id_.toStdString();

        if (std::find(msg.methods.begin(),
                      msg.methods.end(),
                      mtx::events::msg::VerificationMethods::SASv1) == msg.methods.end()) {
                flow->cancelVerification(UnknownMethod);
        }

        return flow;
}
QSharedPointer<DeviceVerificationFlow>
DeviceVerificationFlow::NewToDeviceVerification(QObject *parent_,
                                                const mtx::events::msg::KeyVerificationStart &msg,
                                                QString other_user_,
                                                QString txn_id_)
{
        QSharedPointer<DeviceVerificationFlow> flow(new DeviceVerificationFlow(
          parent_, Type::ToDevice, nullptr, other_user_, QString::fromStdString(msg.from_device)));
        flow->transaction_id = txn_id_.toStdString();

        flow->handleStartMessage(msg, "");

        return flow;
}
QSharedPointer<DeviceVerificationFlow>
DeviceVerificationFlow::InitiateUserVerification(QObject *parent_,
                                                 TimelineModel *timelineModel_,
                                                 QString userid)
{
        QSharedPointer<DeviceVerificationFlow> flow(
          new DeviceVerificationFlow(parent_, Type::RoomMsg, timelineModel_, userid, ""));
        flow->sender = true;
        return flow;
}
QSharedPointer<DeviceVerificationFlow>
DeviceVerificationFlow::InitiateDeviceVerification(QObject *parent_, QString userid, QString device)
{
        QSharedPointer<DeviceVerificationFlow> flow(
          new DeviceVerificationFlow(parent_, Type::ToDevice, nullptr, userid, device));

        flow->sender         = true;
        flow->transaction_id = http::client()->generate_txn_id();

        return flow;
}
