Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@ name = "receive_emails"
required-features = ["internals"]
harness = false

[[bench]]
name = "benchmark_decrypting"
required-features = ["internals"]
harness = false

[[bench]]
name = "get_chat_msgs"
harness = false
Expand Down
199 changes: 199 additions & 0 deletions benches/benchmark_decrypting.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
//! Benchmarks for message decryption,
//! comparing decryption of symmetrically-encrypted messages
//! to decryption of asymmetrically-encrypted messages.
//!
//! Call with
//!
//! ```text
//! cargo bench --bench benchmark_decrypting --features="internals"
//! ```
//!
//! or, if you want to only run e.g. the 'Decrypt a symmetrically encrypted message' benchmark:
//!
//! ```text
//! cargo bench --bench benchmark_decrypting --features="internals" -- 'Decrypt a symmetrically encrypted message'
//! ```
//!
//! You can also pass a substring.
//! So, you can run all 'Decrypt and parse' benchmarks with:
//!
//! ```text
//! cargo bench --bench benchmark_decrypting --features="internals" -- 'Decrypt and parse'
//! ```
//!
//! Symmetric decryption has to try out all known secrets,
//! You can benchmark this by adapting the `NUM_SECRETS` variable.
use std::hint::black_box;

use criterion::{Criterion, criterion_group, criterion_main};
use deltachat::internals_for_benchmarks::create_broadcast_shared_secret;
use deltachat::internals_for_benchmarks::create_dummy_keypair;
use deltachat::internals_for_benchmarks::save_broadcast_shared_secret;
use deltachat::{
Events,
chat::ChatId,
config::Config,
context::Context,
internals_for_benchmarks::key_from_asc,
internals_for_benchmarks::parse_and_get_text,
internals_for_benchmarks::store_self_keypair,
pgp::{KeyPair, decrypt, pk_encrypt, symm_encrypt_message},
stock_str::StockStrings,
};
use rand::{Rng, thread_rng};
use tempfile::tempdir;

const NUM_SECRETS: usize = 500;

async fn create_context() -> Context {
let dir = tempdir().unwrap();
let dbfile = dir.path().join("db.sqlite");
let context = Context::new(dbfile.as_path(), 100, Events::new(), StockStrings::new())
.await
.unwrap();

context
.set_config(Config::ConfiguredAddr, Some("bob@example.net"))
.await
.unwrap();
let secret = key_from_asc(include_str!("../test-data/key/bob-secret.asc")).unwrap();
let public = secret.signed_public_key();
let key_pair = KeyPair { public, secret };
store_self_keypair(&context, &key_pair)
.await
.expect("Failed to save key");

context
}

fn criterion_benchmark(c: &mut Criterion) {
let mut group = c.benchmark_group("Decrypt");

// ===========================================================================================
// Benchmarks for decryption only, without any other parsing
// ===========================================================================================

group.sample_size(10);

group.bench_function("Decrypt a symmetrically encrypted message", |b| {
let plain = generate_plaintext();
let secrets = generate_secrets();
let encrypted = tokio::runtime::Runtime::new().unwrap().block_on(async {
let secret = secrets[NUM_SECRETS / 2].clone();
symm_encrypt_message(
plain.clone(),
black_box(&secret),
create_dummy_keypair("alice@example.org").unwrap().secret,
true,
)
.await
.unwrap()
});

b.iter(|| {
let mut msg =
decrypt(encrypted.clone().into_bytes(), &[], black_box(&secrets)).unwrap();
let decrypted = msg.as_data_vec().unwrap();

assert_eq!(black_box(decrypted), plain);
});
});

group.bench_function("Decrypt a public-key encrypted message", |b| {
let plain = generate_plaintext();
let key_pair = create_dummy_keypair("alice@example.org").unwrap();
let secrets = generate_secrets();
let encrypted = tokio::runtime::Runtime::new().unwrap().block_on(async {
pk_encrypt(
plain.clone(),
vec![black_box(key_pair.public.clone())],
Some(key_pair.secret.clone()),
true,
)
.await
.unwrap()
});

b.iter(|| {
let mut msg = decrypt(
encrypted.clone().into_bytes(),
std::slice::from_ref(&key_pair.secret),
black_box(&secrets),
)
.unwrap();
let decrypted = msg.as_data_vec().unwrap();

assert_eq!(black_box(decrypted), plain);
});
});

// ===========================================================================================
// Benchmarks for the whole parsing pipeline, incl. decryption (but excl. receive_imf())
// ===========================================================================================

let rt = tokio::runtime::Runtime::new().unwrap();
let mut secrets = generate_secrets();

// "secret" is the shared secret that was used to encrypt text_symmetrically_encrypted.eml.
// Put it into the middle of our secrets:
secrets[NUM_SECRETS / 2] = "secret".to_string();

let context = rt.block_on(async {
let context = create_context().await;
for (i, secret) in secrets.iter().enumerate() {
save_broadcast_shared_secret(&context, ChatId::new(10 + i as u32), secret)
.await
.unwrap();
}
context
});

group.bench_function("Decrypt and parse a symmetrically encrypted message", |b| {
b.to_async(&rt).iter(|| {
let ctx = context.clone();
async move {
let text = parse_and_get_text(
&ctx,
include_bytes!("../test-data/message/text_symmetrically_encrypted.eml"),
)
.await
.unwrap();
assert_eq!(text, "Symmetrically encrypted message");
}
});
});

group.bench_function("Decrypt and parse a public-key encrypted message", |b| {
b.to_async(&rt).iter(|| {
let ctx = context.clone();
async move {
let text = parse_and_get_text(
&ctx,
include_bytes!("../test-data/message/text_from_alice_encrypted.eml"),
)
.await
.unwrap();
assert_eq!(text, "hi");
}
});
});

group.finish();
}

fn generate_secrets() -> Vec<String> {
let secrets: Vec<String> = (0..NUM_SECRETS)
.map(|_| create_broadcast_shared_secret())
.collect();
secrets
}

fn generate_plaintext() -> Vec<u8> {
let mut plain: Vec<u8> = vec![0; 500];
thread_rng().fill(&mut plain[..]);
plain
}

criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);
6 changes: 6 additions & 0 deletions deltachat-ffi/src/lot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ impl Lot {
Self::Qr(qr) => match qr {
Qr::AskVerifyContact { .. } => None,
Qr::AskVerifyGroup { grpname, .. } => Some(Cow::Borrowed(grpname)),
Qr::AskJoinBroadcast { broadcast_name, .. } => Some(Cow::Borrowed(broadcast_name)),
Qr::FprOk { .. } => None,
Qr::FprMismatch { .. } => None,
Qr::FprWithoutAddr { fingerprint, .. } => Some(Cow::Borrowed(fingerprint)),
Expand Down Expand Up @@ -99,6 +100,7 @@ impl Lot {
Self::Qr(qr) => match qr {
Qr::AskVerifyContact { .. } => LotState::QrAskVerifyContact,
Qr::AskVerifyGroup { .. } => LotState::QrAskVerifyGroup,
Qr::AskJoinBroadcast { .. } => LotState::QrAskJoinBroadcast,
Qr::FprOk { .. } => LotState::QrFprOk,
Qr::FprMismatch { .. } => LotState::QrFprMismatch,
Qr::FprWithoutAddr { .. } => LotState::QrFprWithoutAddr,
Expand Down Expand Up @@ -126,6 +128,7 @@ impl Lot {
Self::Qr(qr) => match qr {
Qr::AskVerifyContact { contact_id, .. } => contact_id.to_u32(),
Qr::AskVerifyGroup { .. } => Default::default(),
Qr::AskJoinBroadcast { .. } => Default::default(),
Qr::FprOk { contact_id } => contact_id.to_u32(),
Qr::FprMismatch { contact_id } => contact_id.unwrap_or_default().to_u32(),
Qr::FprWithoutAddr { .. } => Default::default(),
Expand Down Expand Up @@ -169,6 +172,9 @@ pub enum LotState {
/// text1=groupname
QrAskVerifyGroup = 202,

/// text1=broadcast_name
QrAskJoinBroadcast = 204,

/// id=contact
QrFprOk = 210,

Expand Down
2 changes: 1 addition & 1 deletion deltachat-jsonrpc/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1010,7 +1010,7 @@ impl CommandApi {
.await
}

/// Create a new **broadcast channel**
/// Create a new, outgoing **broadcast channel**
/// (called "Channel" in the UI).
///
/// Broadcast channels are similar to groups on the sending device,
Expand Down
39 changes: 39 additions & 0 deletions deltachat-jsonrpc/src/api/types/qr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,26 @@ pub enum QrObject {
/// Authentication code.
authcode: String,
},
/// Ask the user whether to join the broadcast channel.
AskJoinBroadcast {
/// Chat name.
broadcast_name: String,
/// A string of random characters,
/// uniquely identifying this broadcast channel in the database.
/// Called `grpid` for historic reasons:
/// The id of multi-user chats is always called `grpid` in the database
/// because groups were once the only multi-user chats.
grpid: String,
/// ID of the contact who owns the channel and created the QR code.
contact_id: u32,
/// Fingerprint of the contact's key as scanned from the QR code.
fingerprint: String,

/// Invite number.
invitenumber: String,
/// Authentication code.
authcode: String,
},
/// Contact fingerprint is verified.
///
/// Ask the user if they want to start chatting.
Expand Down Expand Up @@ -207,6 +227,25 @@ impl From<Qr> for QrObject {
authcode,
}
}
Qr::AskJoinBroadcast {
broadcast_name,
grpid,
contact_id,
fingerprint,
authcode,
invitenumber,
} => {
let contact_id = contact_id.to_u32();
let fingerprint = fingerprint.to_string();
QrObject::AskJoinBroadcast {
broadcast_name,
grpid,
contact_id,
fingerprint,
authcode,
invitenumber,
}
}
Qr::FprOk { contact_id } => {
let contact_id = contact_id.to_u32();
QrObject::FprOk { contact_id }
Expand Down
2 changes: 1 addition & 1 deletion deltachat-rpc-client/src/deltachat_rpc_client/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ def create_group(self, name: str, protect: bool = False) -> Chat:
return Chat(self, self._rpc.create_group_chat(self.id, name, protect))

def create_broadcast(self, name: str) -> Chat:
"""Create a new **broadcast channel**
"""Create a new, outgoing **broadcast channel**
(called "Channel" in the UI).
Broadcast channels are similar to groups on the sending device,
Expand Down
11 changes: 11 additions & 0 deletions deltachat-rpc-client/src/deltachat_rpc_client/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,17 @@ def wait_until_delivered(self) -> None:
if event.kind == EventType.MSG_DELIVERED and event.msg_id == self.id:
break

def resend(self) -> None:
"""Resend messages and make information available for newly added chat members.
Resending sends out the original message, however, recipients and webxdc-status may differ.
Clients that already have the original message can still ignore the resent message as
they have tracked the state by dedicated updates.
Some messages cannot be resent, eg. info-messages, drafts, already pending messages,
or messages that are not sent by SELF.
"""
self._rpc.resend_messages(self.account.id, [self.id])

@futuremethod
def send_webxdc_realtime_advertisement(self):
"""Send an advertisement to join the realtime channel."""
Expand Down
Loading
Loading