Skip to content

Commit ec9d104

Browse files
authored
Basic self-reporting, core part (#5129)
Part of deltachat/deltachat-android#2909 For now, this is only sending a few basic metrics.
1 parent 11214c7 commit ec9d104

File tree

4 files changed

+148
-12
lines changed

4 files changed

+148
-12
lines changed

deltachat-jsonrpc/src/api.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,11 @@ impl CommandApi {
325325
ctx.get_info().await
326326
}
327327

328+
async fn draft_self_report(&self, account_id: u32) -> Result<u32> {
329+
let ctx = self.get_context(account_id).await?;
330+
Ok(ctx.draft_self_report().await?.to_u32())
331+
}
332+
328333
/// Sets the given configuration key.
329334
async fn set_config(&self, account_id: u32, key: String, value: Option<String>) -> Result<()> {
330335
let ctx = self.get_context(account_id).await?;

src/config.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,10 @@ pub enum Config {
347347
/// Row ID of the key in the `keypairs` table
348348
/// used for signatures, encryption to self and included in `Autocrypt` header.
349349
KeyId,
350+
351+
/// This key is sent to the self_reporting bot so that the bot can recognize the user
352+
/// without storing the email address
353+
SelfReportingId,
350354
}
351355

352356
impl Config {

src/context.rs

Lines changed: 118 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,30 @@ use std::time::{Duration, Instant, SystemTime};
1010

1111
use anyhow::{bail, ensure, Context as _, Result};
1212
use async_channel::{self as channel, Receiver, Sender};
13+
use pgp::SignedPublicKey;
1314
use ratelimit::Ratelimit;
1415
use tokio::sync::{Mutex, Notify, RwLock};
1516

16-
use crate::chat::{get_chat_cnt, ChatId};
17+
use crate::aheader::EncryptPreference;
18+
use crate::chat::{get_chat_cnt, ChatId, ProtectionStatus};
1719
use crate::config::Config;
18-
use crate::constants::{DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT, DC_VERSION_STR};
20+
use crate::constants::{
21+
DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT, DC_CHAT_ID_TRASH, DC_VERSION_STR,
22+
};
1923
use crate::contact::Contact;
2024
use crate::debug_logging::DebugLogging;
2125
use crate::events::{Event, EventEmitter, EventType, Events};
2226
use crate::imap::{FolderMeaning, Imap, ServerMetadata};
23-
use crate::key::{load_self_public_key, DcKey as _};
27+
use crate::key::{load_self_public_key, load_self_secret_key, DcKey as _};
2428
use crate::login_param::LoginParam;
25-
use crate::message::{self, MessageState, MsgId};
29+
use crate::message::{self, Message, MessageState, MsgId, Viewtype};
30+
use crate::peerstate::Peerstate;
2631
use crate::quota::QuotaInfo;
2732
use crate::scheduler::{convert_folder_meaning, SchedulerState};
2833
use crate::sql::Sql;
2934
use crate::stock_str::StockStrings;
3035
use crate::timesmearing::SmearedTimestamp;
31-
use crate::tools::{duration_to_str, time};
36+
use crate::tools::{create_id, duration_to_str, time};
3237

3338
/// Builder for the [`Context`].
3439
///
@@ -859,6 +864,91 @@ impl Context {
859864
Ok(res)
860865
}
861866

867+
async fn get_self_report(&self) -> Result<String> {
868+
let mut res = String::new();
869+
res += &format!("core_version {}\n", get_version_str());
870+
871+
let num_msgs: u32 = self
872+
.sql
873+
.query_get_value(
874+
"SELECT COUNT(*) FROM msgs WHERE hidden=0 AND chat_id!=?",
875+
(DC_CHAT_ID_TRASH,),
876+
)
877+
.await?
878+
.unwrap_or_default();
879+
res += &format!("num_msgs {}\n", num_msgs);
880+
881+
let num_chats: u32 = self
882+
.sql
883+
.query_get_value("SELECT COUNT(*) FROM chats WHERE id>9 AND blocked!=1", ())
884+
.await?
885+
.unwrap_or_default();
886+
res += &format!("num_chats {}\n", num_chats);
887+
888+
let db_size = tokio::fs::metadata(&self.sql.dbfile).await?.len();
889+
res += &format!("db_size_bytes {}\n", db_size);
890+
891+
let secret_key = &load_self_secret_key(self).await?.primary_key;
892+
let key_created = secret_key.created_at().timestamp();
893+
res += &format!("key_created {}\n", key_created);
894+
895+
let self_reporting_id = match self.get_config(Config::SelfReportingId).await? {
896+
Some(id) => id,
897+
None => {
898+
let id = create_id();
899+
self.set_config(Config::SelfReportingId, Some(&id)).await?;
900+
id
901+
}
902+
};
903+
res += &format!("self_reporting_id {}", self_reporting_id);
904+
905+
Ok(res)
906+
}
907+
908+
/// Drafts a message with statistics about the usage of Delta Chat.
909+
/// The user can inspect the message if they want, and then hit "Send".
910+
///
911+
/// On the other end, a bot will receive the message and make it available
912+
/// to Delta Chat's developers.
913+
pub async fn draft_self_report(&self) -> Result<ChatId> {
914+
const SELF_REPORTING_BOT: &str = "self_reporting@testrun.org";
915+
916+
let contact_id = Contact::create(self, "Statistics bot", SELF_REPORTING_BOT).await?;
917+
let chat_id = ChatId::create_for_contact(self, contact_id).await?;
918+
919+
// We're including the bot's public key in Delta Chat
920+
// so that the first message to the bot can directly be encrypted:
921+
let public_key = SignedPublicKey::from_base64(
922+
"xjMEZbfBlBYJKwYBBAHaRw8BAQdABpLWS2PUIGGo4pslVt4R8sylP5wZihmhf1DTDr3oCM\
923+
PNHDxzZWxmX3JlcG9ydGluZ0B0ZXN0cnVuLm9yZz7CiwQQFggAMwIZAQUCZbfBlAIbAwQLCQgHBhUI\
924+
CQoLAgMWAgEWIQTS2i16sHeYTckGn284K3M5Z4oohAAKCRA4K3M5Z4oohD8dAQCQV7CoH6UP4PD+Nq\
925+
I4kW5tbbqdh2AnDROg60qotmLExAEAxDfd3QHAK9f8b9qQUbLmHIztCLxhEuVbWPBEYeVW0gvOOARl\
926+
t8GUEgorBgEEAZdVAQUBAQdAMBUhYoAAcI625vGZqnM5maPX4sGJ7qvJxPAFILPy6AcDAQgHwngEGB\
927+
YIACAFAmW3wZQCGwwWIQTS2i16sHeYTckGn284K3M5Z4oohAAKCRA4K3M5Z4oohPwCAQCvzk1ObIkj\
928+
2GqsuIfaULlgdnfdZY8LNary425CEfHZDQD5AblXVrlMO1frdlc/Vo9z3pEeCrfYdD7ITD3/OeVoiQ\
929+
4=",
930+
)?;
931+
let mut peerstate = Peerstate::from_public_key(
932+
SELF_REPORTING_BOT,
933+
0,
934+
EncryptPreference::Mutual,
935+
&public_key,
936+
);
937+
let fingerprint = public_key.fingerprint();
938+
peerstate.set_verified(public_key, fingerprint, "".to_string())?;
939+
peerstate.save_to_db(&self.sql).await?;
940+
chat_id
941+
.set_protection(self, ProtectionStatus::Protected, time(), Some(contact_id))
942+
.await?;
943+
944+
let mut msg = Message::new(Viewtype::Text);
945+
msg.text = self.get_self_report().await?;
946+
947+
chat_id.set_draft(self, Some(&mut msg)).await?;
948+
949+
Ok(chat_id)
950+
}
951+
862952
/// Get a list of fresh, unmuted messages in unblocked chats.
863953
///
864954
/// The list starts with the most recent message
@@ -1129,8 +1219,9 @@ mod tests {
11291219
use crate::constants::Chattype;
11301220
use crate::contact::ContactId;
11311221
use crate::message::{Message, Viewtype};
1222+
use crate::mimeparser::SystemMessage;
11321223
use crate::receive_imf::receive_imf;
1133-
use crate::test_utils::TestContext;
1224+
use crate::test_utils::{get_chat_msg, TestContext};
11341225
use crate::tools::create_outgoing_rfc724_mid;
11351226

11361227
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
@@ -1369,6 +1460,7 @@ mod tests {
13691460
"mail_security",
13701461
"notify_about_wrong_pw",
13711462
"save_mime_headers",
1463+
"self_reporting_id",
13721464
"selfstatus",
13731465
"send_server",
13741466
"send_user",
@@ -1669,4 +1761,24 @@ mod tests {
16691761

16701762
Ok(())
16711763
}
1764+
1765+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1766+
async fn test_draft_self_report() -> Result<()> {
1767+
let alice = TestContext::new_alice().await;
1768+
1769+
let chat_id = alice.draft_self_report().await?;
1770+
let msg = get_chat_msg(&alice, chat_id, 0, 1).await;
1771+
assert_eq!(msg.get_info_type(), SystemMessage::ChatProtectionEnabled);
1772+
1773+
let chat = Chat::load_from_db(&alice, chat_id).await?;
1774+
assert!(chat.is_protected());
1775+
1776+
let mut draft = chat_id.get_draft(&alice).await?.unwrap();
1777+
assert!(draft.text.starts_with("core_version"));
1778+
1779+
// Test that sending into the protected chat works:
1780+
let _sent = alice.send_msg(chat_id, &mut draft).await;
1781+
1782+
Ok(())
1783+
}
16721784
}

src/peerstate.rs

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,28 @@ pub struct Peerstate {
9797
impl Peerstate {
9898
/// Creates a peerstate from the `Autocrypt` header.
9999
pub fn from_header(header: &Aheader, message_time: i64) -> Self {
100+
Self::from_public_key(
101+
&header.addr,
102+
message_time,
103+
header.prefer_encrypt,
104+
&header.public_key,
105+
)
106+
}
107+
108+
/// Creates a peerstate from the given public key.
109+
pub fn from_public_key(
110+
addr: &str,
111+
last_seen: i64,
112+
prefer_encrypt: EncryptPreference,
113+
public_key: &SignedPublicKey,
114+
) -> Self {
100115
Peerstate {
101-
addr: header.addr.clone(),
102-
last_seen: message_time,
103-
last_seen_autocrypt: message_time,
104-
prefer_encrypt: header.prefer_encrypt,
105-
public_key: Some(header.public_key.clone()),
106-
public_key_fingerprint: Some(header.public_key.fingerprint()),
116+
addr: addr.to_string(),
117+
last_seen,
118+
last_seen_autocrypt: last_seen,
119+
prefer_encrypt,
120+
public_key: Some(public_key.clone()),
121+
public_key_fingerprint: Some(public_key.fingerprint()),
107122
gossip_key: None,
108123
gossip_key_fingerprint: None,
109124
gossip_timestamp: 0,

0 commit comments

Comments
 (0)