mirror of
https://github.com/chatmail/core.git
synced 2026-05-14 04:16:30 +03:00
Introduce a state machine for Bob's secure-join
This introduces a state machine which takes care of managing the handshake transitions in the secure-join protocol. This separates user interactions from the protocol state handling. This means that while handling the protocol state there are a bunch of failures no longer possible due to all state information being guaranteed to be present. As part of this the QR-code state has been extracted from the generic Lot structure to something suitable just for the SecureJoin protocol. A LogSink has been added to the testing tools allowing log messages to be correctly displayed on test failures.
This commit is contained in:
@@ -3063,7 +3063,7 @@ mod tests {
|
|||||||
#[async_std::test]
|
#[async_std::test]
|
||||||
async fn test_chat_info() {
|
async fn test_chat_info() {
|
||||||
let t = TestContext::new().await;
|
let t = TestContext::new().await;
|
||||||
let chat = t.chat_with_contact("bob", "bob@example.com").await;
|
let chat = t.create_chat_with_contact("bob", "bob@example.com").await;
|
||||||
let info = chat.get_info(&t).await.unwrap();
|
let info = chat.get_info(&t).await.unwrap();
|
||||||
|
|
||||||
// Ensure we can serialize this.
|
// Ensure we can serialize this.
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ pub struct InnerContext {
|
|||||||
pub(crate) blobdir: PathBuf,
|
pub(crate) blobdir: PathBuf,
|
||||||
pub(crate) sql: Sql,
|
pub(crate) sql: Sql,
|
||||||
pub(crate) os_name: Option<String>,
|
pub(crate) os_name: Option<String>,
|
||||||
pub(crate) bob: RwLock<Bob>,
|
pub(crate) bob: Bob,
|
||||||
pub(crate) last_smeared_timestamp: RwLock<i64>,
|
pub(crate) last_smeared_timestamp: RwLock<i64>,
|
||||||
pub(crate) running_state: RwLock<RunningState>,
|
pub(crate) running_state: RwLock<RunningState>,
|
||||||
/// Mutex to avoid generating the key for the user more than once.
|
/// Mutex to avoid generating the key for the user more than once.
|
||||||
@@ -129,7 +129,7 @@ impl Context {
|
|||||||
os_name: Some(os_name),
|
os_name: Some(os_name),
|
||||||
running_state: RwLock::new(Default::default()),
|
running_state: RwLock::new(Default::default()),
|
||||||
sql: Sql::new(),
|
sql: Sql::new(),
|
||||||
bob: RwLock::new(Default::default()),
|
bob: Default::default(),
|
||||||
last_smeared_timestamp: RwLock::new(0),
|
last_smeared_timestamp: RwLock::new(0),
|
||||||
generating_key_mutex: Mutex::new(()),
|
generating_key_mutex: Mutex::new(()),
|
||||||
oauth2_mutex: Mutex::new(()),
|
oauth2_mutex: Mutex::new(()),
|
||||||
@@ -197,7 +197,10 @@ impl Context {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the next queued event.
|
/// Returns a receiver for emitted events.
|
||||||
|
///
|
||||||
|
/// Multiple emitters can be created, but note that in this case each emitted event will
|
||||||
|
/// only be received by one of the emitters, not by all of them.
|
||||||
pub fn get_event_emitter(&self) -> EventEmitter {
|
pub fn get_event_emitter(&self) -> EventEmitter {
|
||||||
self.events.get_emitter()
|
self.events.get_emitter()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -440,7 +440,6 @@ async fn add_parts(
|
|||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
*hidden = true;
|
*hidden = true;
|
||||||
context.stop_ongoing().await;
|
|
||||||
warn!(context, "Error in Secure-Join message handling: {}", err);
|
warn!(context, "Error in Secure-Join message handling: {}", err);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,17 @@ impl Events {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A receiver of events from a [`Context`].
|
||||||
|
///
|
||||||
|
/// See [`Context::get_event_emitter`] to create an instance. If multiple instances are
|
||||||
|
/// created events emitted by the [`Context`] will only be delivered to one of the
|
||||||
|
/// `EventEmitter`s.
|
||||||
|
///
|
||||||
|
/// The `EventEmitter` is also a [`Stream`], so a typical usage is in a `while let` loop.
|
||||||
|
///
|
||||||
|
/// [`Context`]: crate::context::Context
|
||||||
|
/// [`Context::get_event_emitter`]: crate::context::Context::get_event_emitter
|
||||||
|
/// [`Stream`]: async_std::stream::Stream
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct EventEmitter(Receiver<Event>);
|
pub struct EventEmitter(Receiver<Event>);
|
||||||
|
|
||||||
@@ -120,8 +131,9 @@ impl EventType {
|
|||||||
#[derive(Debug, Clone, PartialEq, Eq, EnumProperty)]
|
#[derive(Debug, Clone, PartialEq, Eq, EnumProperty)]
|
||||||
pub enum EventType {
|
pub enum EventType {
|
||||||
/// The library-user may write an informational string to the log.
|
/// The library-user may write an informational string to the log.
|
||||||
/// Passed to the callback given to dc_context_new().
|
///
|
||||||
/// This event should not be reported to the end-user using a popup or something like that.
|
/// This event should *not* be reported to the end-user using a popup or something like
|
||||||
|
/// that.
|
||||||
#[strum(props(id = "100"))]
|
#[strum(props(id = "100"))]
|
||||||
Info(String),
|
Info(String),
|
||||||
|
|
||||||
@@ -154,14 +166,13 @@ pub enum EventType {
|
|||||||
DeletedBlobFile(String),
|
DeletedBlobFile(String),
|
||||||
|
|
||||||
/// The library-user should write a warning string to the log.
|
/// The library-user should write a warning string to the log.
|
||||||
/// Passed to the callback given to dc_context_new().
|
|
||||||
///
|
///
|
||||||
/// This event should not be reported to the end-user using a popup or something like that.
|
/// This event should *not* be reported to the end-user using a popup or something like
|
||||||
|
/// that.
|
||||||
#[strum(props(id = "300"))]
|
#[strum(props(id = "300"))]
|
||||||
Warning(String),
|
Warning(String),
|
||||||
|
|
||||||
/// The library-user should report an error to the end-user.
|
/// The library-user should report an error to the end-user.
|
||||||
/// Passed to the callback given to dc_context_new().
|
|
||||||
///
|
///
|
||||||
/// As most things are asynchronous, things may go wrong at any time and the user
|
/// As most things are asynchronous, things may go wrong at any time and the user
|
||||||
/// should not be disturbed by a dialog or so. Instead, use a bubble or so.
|
/// should not be disturbed by a dialog or so. Instead, use a bubble or so.
|
||||||
|
|||||||
@@ -2125,7 +2125,7 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let chat = d.chat_with_contact("", "dest@example.com").await;
|
let chat = d.create_chat_with_contact("", "dest@example.com").await;
|
||||||
|
|
||||||
let mut msg = Message::new(Viewtype::Text);
|
let mut msg = Message::new(Viewtype::Text);
|
||||||
|
|
||||||
@@ -2141,7 +2141,7 @@ mod tests {
|
|||||||
let d = test::TestContext::new().await;
|
let d = test::TestContext::new().await;
|
||||||
let ctx = &d.ctx;
|
let ctx = &d.ctx;
|
||||||
|
|
||||||
let chat = d.chat_with_contact("", "dest@example.com").await;
|
let chat = d.create_chat_with_contact("", "dest@example.com").await;
|
||||||
|
|
||||||
let mut msg = Message::new(Viewtype::Text);
|
let mut msg = Message::new(Viewtype::Text);
|
||||||
|
|
||||||
@@ -2331,7 +2331,7 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let chat = d.chat_with_contact("", "dest@example.com").await;
|
let chat = d.create_chat_with_contact("", "dest@example.com").await;
|
||||||
|
|
||||||
let mut msg = Message::new(Viewtype::Text);
|
let mut msg = Message::new(Viewtype::Text);
|
||||||
msg.set_text(Some("Quoted message".to_string()));
|
msg.set_text(Some("Quoted message".to_string()));
|
||||||
|
|||||||
1184
src/securejoin.rs
1184
src/securejoin.rs
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
//! Utilities to help writing tests.
|
//! Utilities to help writing tests.
|
||||||
//!
|
//!
|
||||||
//! This module is only compiled for test runs.
|
//! This private module is only compiled for test runs.
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
@@ -19,7 +19,7 @@ use tempfile::{tempdir, TempDir};
|
|||||||
use crate::chat::{self, Chat, ChatId, ChatItem};
|
use crate::chat::{self, Chat, ChatId, ChatItem};
|
||||||
use crate::chatlist::Chatlist;
|
use crate::chatlist::Chatlist;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::constants::DC_CONTACT_ID_SELF;
|
use crate::constants::{Viewtype, DC_CONTACT_ID_SELF, DC_MSG_ID_DAYMARKER, DC_MSG_ID_MARKER1};
|
||||||
use crate::contact::{Contact, Origin};
|
use crate::contact::{Contact, Origin};
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::dc_receive_imf::dc_receive_imf;
|
use crate::dc_receive_imf::dc_receive_imf;
|
||||||
@@ -31,10 +31,6 @@ use crate::message::{update_msg_state, Message, MessageState, MsgId};
|
|||||||
use crate::mimeparser::MimeMessage;
|
use crate::mimeparser::MimeMessage;
|
||||||
use crate::param::{Param, Params};
|
use crate::param::{Param, Params};
|
||||||
|
|
||||||
use crate::constants::Viewtype;
|
|
||||||
use crate::constants::DC_MSG_ID_DAYMARKER;
|
|
||||||
use crate::constants::DC_MSG_ID_MARKER1;
|
|
||||||
|
|
||||||
type EventSink =
|
type EventSink =
|
||||||
dyn Fn(Event) -> Pin<Box<dyn Future<Output = ()> + Send + 'static>> + Send + Sync + 'static;
|
dyn Fn(Event) -> Pin<Box<dyn Future<Output = ()> + Send + 'static>> + Send + Sync + 'static;
|
||||||
|
|
||||||
@@ -67,7 +63,7 @@ impl fmt::Debug for TestContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TestContext {
|
impl TestContext {
|
||||||
/// Creates a new [TestContext].
|
/// Creates a new [`TestContext`].
|
||||||
///
|
///
|
||||||
/// The [Context] will be created and have an SQLite database named "db.sqlite" in the
|
/// The [Context] will be created and have an SQLite database named "db.sqlite" in the
|
||||||
/// [TestContext.dir] directory. This directory is cleaned up when the [TestContext] is
|
/// [TestContext.dir] directory. This directory is cleaned up when the [TestContext] is
|
||||||
@@ -120,7 +116,7 @@ impl TestContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new configured [TestContext].
|
/// Creates a new configured [TestContext].
|
||||||
///
|
///
|
||||||
/// This is a shortcut which automatically calls [TestContext::configure_alice] after
|
/// This is a shortcut which automatically calls [TestContext::configure_alice] after
|
||||||
/// creating the context.
|
/// creating the context.
|
||||||
@@ -130,7 +126,7 @@ impl TestContext {
|
|||||||
t
|
t
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new configured [TestContext].
|
/// Creates a new configured [TestContext].
|
||||||
///
|
///
|
||||||
/// This is a shortcut which configures bob@example.net with a fixed key.
|
/// This is a shortcut which configures bob@example.net with a fixed key.
|
||||||
pub async fn new_bob() -> Self {
|
pub async fn new_bob() -> Self {
|
||||||
@@ -200,7 +196,7 @@ impl TestContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve a sent message from the jobs table.
|
/// Retrieves a sent message from the jobs table.
|
||||||
///
|
///
|
||||||
/// This retrieves and removes a message which has been scheduled to send from the jobs
|
/// This retrieves and removes a message which has been scheduled to send from the jobs
|
||||||
/// table. Messages are returned in the order they have been sent.
|
/// table. Messages are returned in the order they have been sent.
|
||||||
@@ -258,7 +254,7 @@ impl TestContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a message.
|
/// Parses a message.
|
||||||
///
|
///
|
||||||
/// Parsing a message does not run the entire receive pipeline, but is not without
|
/// Parsing a message does not run the entire receive pipeline, but is not without
|
||||||
/// side-effects either. E.g. if the message includes autocrypt headers the relevant
|
/// side-effects either. E.g. if the message includes autocrypt headers the relevant
|
||||||
@@ -286,7 +282,7 @@ impl TestContext {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the most recent message of a chat.
|
/// Gets the most recent message of a chat.
|
||||||
///
|
///
|
||||||
/// Panics on errors or if the most recent message is a marker.
|
/// Panics on errors or if the most recent message is a marker.
|
||||||
pub async fn get_last_msg_in(&self, chat_id: ChatId) -> Message {
|
pub async fn get_last_msg_in(&self, chat_id: ChatId) -> Message {
|
||||||
@@ -299,13 +295,17 @@ impl TestContext {
|
|||||||
Message::load_from_db(&self.ctx, *msg_id).await.unwrap()
|
Message::load_from_db(&self.ctx, *msg_id).await.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the most recent message over all chats.
|
/// Gets the most recent message over all chats.
|
||||||
pub async fn get_last_msg(&self) -> Message {
|
pub async fn get_last_msg(&self) -> Message {
|
||||||
let chats = Chatlist::try_load(&self.ctx, 0, None, None).await.unwrap();
|
let chats = Chatlist::try_load(&self.ctx, 0, None, None).await.unwrap();
|
||||||
let msg_id = chats.get_msg_id(chats.len() - 1).unwrap();
|
let msg_id = chats.get_msg_id(chats.len() - 1).unwrap();
|
||||||
Message::load_from_db(&self.ctx, msg_id).await.unwrap()
|
Message::load_from_db(&self.ctx, msg_id).await.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates or returns an existing 1:1 [`Chat`] with another account.
|
||||||
|
///
|
||||||
|
/// This first creates a contact using the configured details on the other account, then
|
||||||
|
/// creates a 1:1 chat with this contact.
|
||||||
pub async fn create_chat(&self, other: &TestContext) -> Chat {
|
pub async fn create_chat(&self, other: &TestContext) -> Chat {
|
||||||
let (contact_id, _modified) = Contact::add_or_lookup(
|
let (contact_id, _modified) = Contact::add_or_lookup(
|
||||||
self,
|
self,
|
||||||
@@ -324,7 +324,11 @@ impl TestContext {
|
|||||||
Chat::load_from_db(self, chat_id).await.unwrap()
|
Chat::load_from_db(self, chat_id).await.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn chat_with_contact(&self, name: &str, addr: &str) -> Chat {
|
/// Creates or returns an existing [`Contact`] and 1:1 [`Chat`] with another email.
|
||||||
|
///
|
||||||
|
/// This first creates a contact from the `name` and `addr` and then creates a 1:1 chat
|
||||||
|
/// with this contact.
|
||||||
|
pub async fn create_chat_with_contact(&self, name: &str, addr: &str) -> Chat {
|
||||||
let contact = Contact::create(self, name, addr)
|
let contact = Contact::create(self, name, addr)
|
||||||
.await
|
.await
|
||||||
.expect("failed to create contact");
|
.expect("failed to create contact");
|
||||||
@@ -332,6 +336,7 @@ impl TestContext {
|
|||||||
Chat::load_from_db(self, chat_id).await.unwrap()
|
Chat::load_from_db(self, chat_id).await.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieves the "self" chat.
|
||||||
pub async fn get_self_chat(&self) -> Chat {
|
pub async fn get_self_chat(&self) -> Chat {
|
||||||
let chat_id = chat::create_by_contact_id(self, DC_CONTACT_ID_SELF)
|
let chat_id = chat::create_by_contact_id(self, DC_CONTACT_ID_SELF)
|
||||||
.await
|
.await
|
||||||
@@ -339,7 +344,11 @@ impl TestContext {
|
|||||||
Chat::load_from_db(self, chat_id).await.unwrap()
|
Chat::load_from_db(self, chat_id).await.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends out the text message. If the other side shall receive it, you have to call `recv_msg()` with the returned `SentMessage`.
|
/// Sends out the text message.
|
||||||
|
///
|
||||||
|
/// This is not hooked up to any SMTP-IMAP pipeline, so the other account must call
|
||||||
|
/// [`TestContext::recv_msg`] with the returned [`SentMessage`] if it wants to receive
|
||||||
|
/// the message.
|
||||||
pub async fn send_text(&self, chat_id: ChatId, txt: &str) -> SentMessage {
|
pub async fn send_text(&self, chat_id: ChatId, txt: &str) -> SentMessage {
|
||||||
let mut msg = Message::new(Viewtype::Text);
|
let mut msg = Message::new(Viewtype::Text);
|
||||||
msg.set_text(Some(txt.to_string()));
|
msg.set_text(Some(txt.to_string()));
|
||||||
@@ -348,8 +357,11 @@ impl TestContext {
|
|||||||
self.pop_sent_msg().await
|
self.pop_sent_msg().await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// You can use this to debug your test by printing a chat structure
|
/// Prints out the entire chat to stdout.
|
||||||
// This code is mainly the same as `log_msglist` in `cmdline.rs`, so one day, we could merge them to a public function in the `deltachat` crate.
|
///
|
||||||
|
/// You can use this to debug your test by printing the entire chat conversation.
|
||||||
|
// This code is mainly the same as `log_msglist` in `cmdline.rs`, so one day, we could
|
||||||
|
// merge them to a public function in the `deltachat` crate.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub async fn print_chat(&self, chat: &Chat) {
|
pub async fn print_chat(&self, chat: &Chat) {
|
||||||
let msglist = chat::get_chat_msgs(&self, chat.get_id(), 0x1, None).await;
|
let msglist = chat::get_chat_msgs(&self, chat.get_id(), 0x1, None).await;
|
||||||
@@ -432,7 +444,7 @@ impl SentMessage {
|
|||||||
/// This saves CPU cycles by avoiding having to generate a key.
|
/// This saves CPU cycles by avoiding having to generate a key.
|
||||||
///
|
///
|
||||||
/// The keypair was created using the crate::key::tests::gen_key test.
|
/// The keypair was created using the crate::key::tests::gen_key test.
|
||||||
pub(crate) fn alice_keypair() -> key::KeyPair {
|
pub fn alice_keypair() -> key::KeyPair {
|
||||||
let addr = EmailAddress::new("alice@example.com").unwrap();
|
let addr = EmailAddress::new("alice@example.com").unwrap();
|
||||||
let public =
|
let public =
|
||||||
key::SignedPublicKey::from_base64(include_str!("../test-data/key/alice-public.asc"))
|
key::SignedPublicKey::from_base64(include_str!("../test-data/key/alice-public.asc"))
|
||||||
@@ -450,7 +462,7 @@ pub(crate) fn alice_keypair() -> key::KeyPair {
|
|||||||
/// Load a pre-generated keypair for bob@example.net from disk.
|
/// Load a pre-generated keypair for bob@example.net from disk.
|
||||||
///
|
///
|
||||||
/// Like [alice_keypair] but a different key and identity.
|
/// Like [alice_keypair] but a different key and identity.
|
||||||
pub(crate) fn bob_keypair() -> key::KeyPair {
|
pub fn bob_keypair() -> key::KeyPair {
|
||||||
let addr = EmailAddress::new("bob@example.net").unwrap();
|
let addr = EmailAddress::new("bob@example.net").unwrap();
|
||||||
let public =
|
let public =
|
||||||
key::SignedPublicKey::from_base64(include_str!("../test-data/key/bob-public.asc")).unwrap();
|
key::SignedPublicKey::from_base64(include_str!("../test-data/key/bob-public.asc")).unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user