Skip to content

Commit 5ad71e0

Browse files
committed
Add message stats. Tests and splitting by bot are missing.
1 parent 39afd27 commit 5ad71e0

File tree

5 files changed

+143
-50
lines changed

5 files changed

+143
-50
lines changed

src/config.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,9 @@ pub enum Config {
439439
/// without storing the email address
440440
SelfReportingId,
441441

442+
/// Timestamp of enabling SelfReporting.
443+
SelfReportingEnabledTimestamp,
444+
442445
/// MsgId of webxdc map integration.
443446
WebxdcIntegration,
444447

@@ -831,6 +834,12 @@ impl Context {
831834
.await?;
832835
}
833836
}
837+
Config::SelfReporting => {
838+
self.sql.set_raw_config(key.as_ref(), value).await?;
839+
self.sql
840+
.set_raw_config(Config::SelfReportingEnabledTimestamp.as_ref(), value)
841+
.await?;
842+
}
834843
_ => {
835844
self.sql.set_raw_config(key.as_ref(), value).await?;
836845
}

src/context.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,6 +1048,12 @@ impl Context {
10481048
.await?
10491049
.to_string(),
10501050
);
1051+
res.insert(
1052+
"self_reporting_enabled_timestamp",
1053+
self.get_config_i64(Config::SelfReportingEnabledTimestamp)
1054+
.await?
1055+
.to_string(),
1056+
);
10511057

10521058
let elapsed = time_elapsed(&self.creation_time);
10531059
res.insert("uptime", duration_to_str(elapsed));

src/scheduler.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -508,7 +508,7 @@ async fn inbox_fetch_idle(ctx: &Context, imap: &mut Imap, mut session: Session)
508508
}
509509
};
510510

511-
maybe_send_self_report(ctx).await?;
511+
maybe_send_self_report(ctx).await.log_err(ctx).ok();
512512
match ctx.get_config_bool(Config::FetchedExistingMsgs).await {
513513
Ok(fetched_existing_msgs) => {
514514
if !fetched_existing_msgs {

src/self_reporting.rs

Lines changed: 117 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use std::collections::{BTreeMap, BTreeSet};
44

5-
use anyhow::{Context as _, Result};
5+
use anyhow::{ensure, Context as _, Result};
66
use pgp::types::PublicKeyTrait;
77
use serde::Serialize;
88

@@ -30,7 +30,8 @@ struct Statistics {
3030
key_created: i64,
3131
chat_numbers: ChatNumbers,
3232
self_reporting_id: String,
33-
contact_infos: Vec<ContactInfo>,
33+
contact_stats: Vec<ContactStat>,
34+
message_stats: MessageStats,
3435
}
3536
#[derive(Default, Serialize)]
3637
struct ChatNumbers {
@@ -52,7 +53,7 @@ enum VerifiedStatus {
5253
}
5354

5455
#[derive(Serialize)]
55-
struct ContactInfo {
56+
struct ContactStat {
5657
#[serde(skip_serializing)]
5758
id: ContactId,
5859

@@ -61,16 +62,16 @@ struct ContactInfo {
6162
direct_chat: bool,
6263
last_seen: u64,
6364

64-
//new: bool, // TODO
6565
#[serde(skip_serializing_if = "Option::is_none")]
6666
transitive_chain: Option<u32>,
67+
//new: bool, // TODO
6768
}
6869

69-
async fn get_contact_infos(context: &Context) -> Result<Vec<ContactInfo>> {
70+
async fn get_contact_stats(context: &Context) -> Result<Vec<ContactStat>> {
7071
let mut verified_by_map: BTreeMap<ContactId, ContactId> = BTreeMap::new();
7172
let mut bot_ids: BTreeSet<ContactId> = BTreeSet::new();
7273

73-
let mut contacts: Vec<ContactInfo> = context
74+
let mut contacts: Vec<ContactStat> = context
7475
.sql
7576
.query_map(
7677
"SELECT id, fingerprint<>'', verifier, last_seen, is_bot FROM contacts c
@@ -98,7 +99,7 @@ async fn get_contact_infos(context: &Context) -> Result<Vec<ContactInfo>> {
9899
bot_ids.insert(id);
99100
}
100101

101-
Ok(ContactInfo {
102+
Ok(ContactStat {
102103
id,
103104
verified,
104105
bot,
@@ -165,40 +166,133 @@ async fn get_contact_infos(context: &Context) -> Result<Vec<ContactInfo>> {
165166
Ok(contacts)
166167
}
167168

169+
#[derive(Serialize)]
170+
struct MessageStats {
171+
to_verified: u32,
172+
unverified_encrypted: u32,
173+
unencrypted: u32,
174+
}
175+
176+
async fn get_message_stats(context: &Context) -> Result<MessageStats> {
177+
let enabled_ts: i64 = context
178+
.get_config_i64(Config::SelfReportingEnabledTimestamp)
179+
.await?;
180+
ensure!(enabled_ts > 0, "Enabled Timestamp missing");
181+
182+
let selfreporting_bot_chat_id = get_selfreporting_bot(context).await?;
183+
184+
let trans_fn = |t: &mut rusqlite::Transaction| {
185+
t.pragma_update(None, "query_only", "0")?;
186+
t.execute(
187+
"CREATE TEMP TABLE temp.verified_chats (
188+
id INTEGER PRIMARY KEY
189+
) STRICT",
190+
(),
191+
)?;
192+
193+
t.execute(
194+
"INSERT INTO temp.verified_chats
195+
SELECT id FROM chats
196+
WHERE protected=1 AND id>9",
197+
(),
198+
)?;
199+
200+
let to_verified = t.query_row(
201+
"SELECT COUNT(*) FROM msgs
202+
WHERE chat_id IN temp.verified_chats
203+
AND chat_id<>? AND id>9 AND timestamp_sent>?",
204+
(selfreporting_bot_chat_id, enabled_ts),
205+
|row| row.get(0),
206+
)?;
207+
208+
let unverified_encrypted = t.query_row(
209+
"SELECT COUNT(*) FROM msgs
210+
WHERE chat_id not IN temp.verified_chats
211+
AND (param GLOB '*\nc=1*' OR param GLOB 'c=1*')
212+
AND chat_id<>? AND id>9 AND timestamp_sent>?",
213+
(selfreporting_bot_chat_id, enabled_ts),
214+
|row| row.get(0),
215+
)?;
216+
217+
let unencrypted = t.query_row(
218+
"SELECT COUNT(*) FROM msgs
219+
WHERE chat_id not IN temp.verified_chats
220+
AND NOT (param GLOB '*\nc=1*' OR param GLOB 'c=1*')
221+
AND chat_id<>? AND id>9 AND timestamp_sent>=?",
222+
(selfreporting_bot_chat_id, enabled_ts),
223+
|row| row.get(0),
224+
)?;
225+
226+
t.execute("DROP TABLE temp.verified_chats", ())?;
227+
228+
Ok(MessageStats {
229+
to_verified,
230+
unverified_encrypted,
231+
unencrypted,
232+
})
233+
};
234+
235+
let query_only = true;
236+
let message_stats: MessageStats = context.sql.transaction_ex(query_only, trans_fn).await?;
237+
238+
Ok(message_stats)
239+
}
240+
168241
/// Sends a message with statistics about the usage of Delta Chat,
169242
/// if the last time such a message was sent
170243
/// was more than a week ago.
171244
///
172245
/// On the other end, a bot will receive the message and make it available
173246
/// to Delta Chat's developers.
174-
pub async fn maybe_send_self_report(context: &Context) -> Result<()> {
247+
pub async fn maybe_send_self_report(context: &Context) -> Result<Option<ChatId>> {
175248
//#[cfg(target_os = "android")] TODO
176249
if context.get_config_bool(Config::SelfReporting).await? {
177-
match context.get_config_i64(Config::LastSelfReportSent).await {
178-
Ok(last_selfreport_time) => {
179-
let next_selfreport_time = last_selfreport_time.saturating_add(30); // TODO increase to 1 day or 1 week
180-
if next_selfreport_time <= time() {
181-
send_self_report(context).await?;
182-
}
183-
}
184-
Err(err) => {
185-
warn!(context, "Failed to get last self_reporting time: {}", err);
186-
}
250+
let last_selfreport_time = context.get_config_i64(Config::LastSelfReportSent).await?;
251+
let next_selfreport_time = last_selfreport_time.saturating_add(30); // TODO increase to 1 day or 1 week
252+
if next_selfreport_time <= time() {
253+
return Ok(Some(send_self_report(context).await?));
187254
}
188255
}
189-
Ok(())
256+
Ok(None)
190257
}
191258

192259
async fn send_self_report(context: &Context) -> Result<ChatId> {
193260
info!(context, "Sending self report.");
194-
// Setting `Config::LastHousekeeping` at the beginning avoids endless loops when things do not
195-
// work out for whatever reason or are interrupted by the OS.
261+
// Setting this config at the beginning avoids endless loops when things do not
262+
// work out for whatever reason.
196263
context
197264
.set_config_internal(Config::LastSelfReportSent, Some(&time().to_string()))
198265
.await
199266
.log_err(context)
200267
.ok();
201268

269+
let chat_id = get_selfreporting_bot(context).await?;
270+
271+
let mut msg = Message::new(Viewtype::File);
272+
msg.set_text(
273+
"The attachment contains anonymous usage statistics, \
274+
because you enabled this in the settings. \
275+
This helps us improve the security of Delta Chat. \
276+
See TODO[blog post] for more information."
277+
.to_string(),
278+
);
279+
msg.set_file_from_bytes(
280+
context,
281+
"statistics.txt",
282+
get_self_report(context).await?.as_bytes(),
283+
Some("text/plain"),
284+
)?;
285+
286+
crate::chat::send_msg(context, chat_id, &mut msg)
287+
.await
288+
.context("Failed to send self_reporting message")
289+
.log_err(context)
290+
.ok();
291+
292+
Ok(chat_id)
293+
}
294+
295+
async fn get_selfreporting_bot(context: &Context) -> Result<ChatId, anyhow::Error> {
202296
let contact_id: ContactId = *import_vcard(context, SELF_REPORTING_BOT_VCARD)
203297
.await?
204298
.first()
@@ -226,27 +320,6 @@ async fn send_self_report(context: &Context) -> Result<ChatId> {
226320
)
227321
.await?;
228322

229-
let mut msg = Message::new(Viewtype::File);
230-
msg.set_text(
231-
"The attachment contains anonymous usage statistics, \
232-
because you enabled this in the settings. \
233-
This helps us improve the security of Delta Chat. \
234-
See TODO[blog post] for more information."
235-
.to_string(),
236-
);
237-
msg.set_file_from_bytes(
238-
context,
239-
"statistics.txt",
240-
get_self_report(context).await?.as_bytes(),
241-
Some("text/plain"),
242-
)?;
243-
244-
crate::chat::send_msg(context, chat_id, &mut msg)
245-
.await
246-
.context("Failed to send self_reporting message")
247-
.log_err(context)
248-
.ok();
249-
250323
Ok(chat_id)
251324
}
252325

@@ -355,7 +428,8 @@ async fn get_self_report(context: &Context) -> Result<String> {
355428
key_created,
356429
chat_numbers,
357430
self_reporting_id,
358-
contact_infos: get_contact_infos(context).await?,
431+
contact_stats: get_contact_stats(context).await?,
432+
message_stats: get_message_stats(context).await?,
359433
};
360434

361435
Ok(serde_json::to_string_pretty(&statistics)?)

src/self_reporting/self_reporting_tests.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ async fn test_send_self_report() -> Result<()> {
99
let alice = &tcm.alice().await;
1010
let bob = &tcm.bob().await;
1111

12-
let chat_id = send_self_report(&alice).await?;
12+
alice.set_config_bool(Config::SelfReporting, true).await?;
13+
14+
let chat_id = maybe_send_self_report(&alice).await?.unwrap();
1315
let msg = get_chat_msg(&alice, chat_id, 0, 2).await;
1416
assert_eq!(msg.get_info_type(), SystemMessage::ChatProtectionEnabled);
1517

@@ -22,15 +24,17 @@ async fn test_send_self_report() -> Result<()> {
2224
let report = tokio::fs::read(msg.get_file(&alice).unwrap()).await?;
2325
let report = std::str::from_utf8(&report)?;
2426
println!("\nEmpty account:\n{}\n", report);
25-
assert!(report.contains(r#""contact_infos": []"#));
27+
assert!(report.contains(r#""contact_stats": []"#));
2628

2729
let r: serde_json::Value = serde_json::from_str(&report)?;
2830
assert_eq!(
29-
r.get("contact_infos").unwrap(),
31+
r.get("contact_stats").unwrap(),
3032
&serde_json::Value::Array(vec![])
3133
);
3234
assert_eq!(r.get("core_version").unwrap(), get_version_str());
3335

36+
assert_eq!(maybe_send_self_report(alice).await?, None);
37+
3438
tcm.send_recv_accept(bob, alice, "Hi!").await;
3539

3640
let report = get_self_report(alice).await?;
@@ -45,9 +49,9 @@ async fn test_send_self_report() -> Result<()> {
4549
r.get("self_reporting_id").unwrap(),
4650
r2.get("self_reporting_id").unwrap()
4751
);
48-
let contact_infos = r2.get("contact_infos").unwrap().as_array().unwrap();
49-
assert_eq!(contact_infos.len(), 1);
50-
let contact_info = &contact_infos[0];
52+
let contact_stats = r2.get("contact_stats").unwrap().as_array().unwrap();
53+
assert_eq!(contact_stats.len(), 1);
54+
let contact_info = &contact_stats[0];
5155
assert_eq!(
5256
contact_info.get("bot").unwrap(),
5357
&serde_json::Value::Bool(false)

0 commit comments

Comments
 (0)