mirror of
https://github.com/chatmail/core.git
synced 2026-04-17 21:46:35 +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 fn test_chat_info() {
|
||||
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();
|
||||
|
||||
// Ensure we can serialize this.
|
||||
|
||||
@@ -47,7 +47,7 @@ pub struct InnerContext {
|
||||
pub(crate) blobdir: PathBuf,
|
||||
pub(crate) sql: Sql,
|
||||
pub(crate) os_name: Option<String>,
|
||||
pub(crate) bob: RwLock<Bob>,
|
||||
pub(crate) bob: Bob,
|
||||
pub(crate) last_smeared_timestamp: RwLock<i64>,
|
||||
pub(crate) running_state: RwLock<RunningState>,
|
||||
/// Mutex to avoid generating the key for the user more than once.
|
||||
@@ -129,7 +129,7 @@ impl Context {
|
||||
os_name: Some(os_name),
|
||||
running_state: RwLock::new(Default::default()),
|
||||
sql: Sql::new(),
|
||||
bob: RwLock::new(Default::default()),
|
||||
bob: Default::default(),
|
||||
last_smeared_timestamp: RwLock::new(0),
|
||||
generating_key_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 {
|
||||
self.events.get_emitter()
|
||||
}
|
||||
|
||||
@@ -440,7 +440,6 @@ async fn add_parts(
|
||||
}
|
||||
Err(err) => {
|
||||
*hidden = true;
|
||||
context.stop_ongoing().await;
|
||||
warn!(context, "Error in Secure-Join message handling: {}", err);
|
||||
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)]
|
||||
pub struct EventEmitter(Receiver<Event>);
|
||||
|
||||
@@ -120,8 +131,9 @@ impl EventType {
|
||||
#[derive(Debug, Clone, PartialEq, Eq, EnumProperty)]
|
||||
pub enum EventType {
|
||||
/// 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"))]
|
||||
Info(String),
|
||||
|
||||
@@ -154,14 +166,13 @@ pub enum EventType {
|
||||
DeletedBlobFile(String),
|
||||
|
||||
/// 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"))]
|
||||
Warning(String),
|
||||
|
||||
/// 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
|
||||
/// should not be disturbed by a dialog or so. Instead, use a bubble or so.
|
||||
|
||||
@@ -2125,7 +2125,7 @@ mod tests {
|
||||
.await
|
||||
.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);
|
||||
|
||||
@@ -2141,7 +2141,7 @@ mod tests {
|
||||
let d = test::TestContext::new().await;
|
||||
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);
|
||||
|
||||
@@ -2331,7 +2331,7 @@ mod tests {
|
||||
.await
|
||||
.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);
|
||||
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.
|
||||
//!
|
||||
//! This module is only compiled for test runs.
|
||||
//! This private module is only compiled for test runs.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt;
|
||||
@@ -19,7 +19,7 @@ use tempfile::{tempdir, TempDir};
|
||||
use crate::chat::{self, Chat, ChatId, ChatItem};
|
||||
use crate::chatlist::Chatlist;
|
||||
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::context::Context;
|
||||
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::param::{Param, Params};
|
||||
|
||||
use crate::constants::Viewtype;
|
||||
use crate::constants::DC_MSG_ID_DAYMARKER;
|
||||
use crate::constants::DC_MSG_ID_MARKER1;
|
||||
|
||||
type EventSink =
|
||||
dyn Fn(Event) -> Pin<Box<dyn Future<Output = ()> + Send + 'static>> + Send + Sync + 'static;
|
||||
|
||||
@@ -67,7 +63,7 @@ impl fmt::Debug for 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
|
||||
/// [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
|
||||
/// creating the context.
|
||||
@@ -130,7 +126,7 @@ impl TestContext {
|
||||
t
|
||||
}
|
||||
|
||||
/// Create a new configured [TestContext].
|
||||
/// Creates a new configured [TestContext].
|
||||
///
|
||||
/// This is a shortcut which configures bob@example.net with a fixed key.
|
||||
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
|
||||
/// 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
|
||||
/// side-effects either. E.g. if the message includes autocrypt headers the relevant
|
||||
@@ -286,7 +282,7 @@ impl TestContext {
|
||||
.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.
|
||||
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()
|
||||
}
|
||||
|
||||
/// Get the most recent message over all chats.
|
||||
/// Gets the most recent message over all chats.
|
||||
pub async fn get_last_msg(&self) -> Message {
|
||||
let chats = Chatlist::try_load(&self.ctx, 0, None, None).await.unwrap();
|
||||
let msg_id = chats.get_msg_id(chats.len() - 1).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 {
|
||||
let (contact_id, _modified) = Contact::add_or_lookup(
|
||||
self,
|
||||
@@ -324,7 +324,11 @@ impl TestContext {
|
||||
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)
|
||||
.await
|
||||
.expect("failed to create contact");
|
||||
@@ -332,6 +336,7 @@ impl TestContext {
|
||||
Chat::load_from_db(self, chat_id).await.unwrap()
|
||||
}
|
||||
|
||||
/// Retrieves the "self" chat.
|
||||
pub async fn get_self_chat(&self) -> Chat {
|
||||
let chat_id = chat::create_by_contact_id(self, DC_CONTACT_ID_SELF)
|
||||
.await
|
||||
@@ -339,7 +344,11 @@ impl TestContext {
|
||||
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 {
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.set_text(Some(txt.to_string()));
|
||||
@@ -348,8 +357,11 @@ impl TestContext {
|
||||
self.pop_sent_msg().await
|
||||
}
|
||||
|
||||
/// You can use this to debug your test by printing a chat structure
|
||||
// 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.
|
||||
/// Prints out the entire chat to stdout.
|
||||
///
|
||||
/// 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)]
|
||||
pub async fn print_chat(&self, chat: &Chat) {
|
||||
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.
|
||||
///
|
||||
/// 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 public =
|
||||
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.
|
||||
///
|
||||
/// 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 public =
|
||||
key::SignedPublicKey::from_base64(include_str!("../test-data/key/bob-public.asc")).unwrap();
|
||||
|
||||
Reference in New Issue
Block a user