11//! TODO doc comment
22
3+ use std:: collections:: { BTreeMap , BTreeSet } ;
4+
35use anyhow:: { Context as _, Result } ;
46use pgp:: types:: PublicKeyTrait ;
57use serde:: Serialize ;
68
79use crate :: chat:: { self , ChatId , ChatVisibility , MuteDuration , ProtectionStatus } ;
810use crate :: config:: Config ;
9- use crate :: constants:: DC_CHAT_ID_TRASH ;
10- use crate :: contact:: { import_vcard, mark_contact_id_as_verified, ContactId } ;
11+ use crate :: constants:: { Chattype , DC_CHAT_ID_TRASH } ;
12+ use crate :: contact:: { import_vcard, mark_contact_id_as_verified, ContactId , Origin } ;
1113use crate :: context:: { get_version_str, Context } ;
1214use crate :: download:: DownloadState ;
1315use crate :: key:: load_self_public_key;
@@ -16,6 +18,9 @@ use crate::message::{Message, Viewtype};
1618use crate :: param:: { Param , Params } ;
1719use crate :: tools:: { create_id, time} ;
1820
21+ pub ( crate ) const SELF_REPORTING_BOT_EMAIL : & str = "self_reporting@testrun.org" ;
22+ const SELF_REPORTING_BOT_VCARD : & str = include_str ! ( "../assets/self-reporting-bot.vcf" ) ;
23+
1924#[ derive( Serialize ) ]
2025struct Statistics {
2126 core_version : String ,
@@ -25,6 +30,7 @@ struct Statistics {
2530 key_created : i64 ,
2631 chat_numbers : ChatNumbers ,
2732 self_reporting_id : String ,
33+ contact_infos : Vec < ContactInfo > ,
2834}
2935#[ derive( Default , Serialize ) ]
3036struct ChatNumbers {
@@ -36,6 +42,132 @@ struct ChatNumbers {
3642 unencrypted_mua : u32 ,
3743}
3844
45+ #[ derive( Serialize , PartialEq ) ]
46+ enum VerifiedStatus {
47+ Direct ,
48+ Transitive ,
49+ TransitiveViaBot ,
50+ Opportunistic ,
51+ Unencrypted ,
52+ }
53+
54+ #[ derive( Serialize ) ]
55+ struct ContactInfo {
56+ #[ serde( skip_serializing) ]
57+ id : ContactId ,
58+
59+ verified : VerifiedStatus ,
60+
61+ #[ serde( skip_serializing) ]
62+ verifier : ContactId , // TODO unused, could be removed
63+ bot : bool ,
64+ direct_chat : bool ,
65+ last_seen : u64 ,
66+ //new: bool, // TODO
67+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
68+ transitive_chain : Option < u32 > ,
69+ }
70+
71+ async fn get_contact_infos ( context : & Context ) -> Result < Vec < ContactInfo > > {
72+ let mut verified_by_map: BTreeMap < ContactId , ContactId > = BTreeMap :: new ( ) ;
73+ let mut bot_ids: BTreeSet < ContactId > = BTreeSet :: new ( ) ;
74+
75+ let mut contacts: Vec < ContactInfo > = context
76+ . sql
77+ . query_map (
78+ "SELECT id, fingerprint<>'', verifier, last_seen, is_bot FROM contacts c
79+ WHERE id>9 AND origin>? AND addr<>?" ,
80+ ( Origin :: Hidden , SELF_REPORTING_BOT_EMAIL ) ,
81+ |row| {
82+ let id = row. get ( 0 ) ?;
83+ let is_encrypted: bool = row. get ( 1 ) ?;
84+ let verifier: ContactId = row. get ( 2 ) ?;
85+ let last_seen: u64 = row. get ( 3 ) ?;
86+ let bot: bool = row. get ( 4 ) ?;
87+
88+ let verified = match ( is_encrypted, verifier) {
89+ ( true , ContactId :: SELF ) => VerifiedStatus :: Direct ,
90+ ( true , ContactId :: UNDEFINED ) => VerifiedStatus :: Opportunistic ,
91+ ( true , _) => VerifiedStatus :: Transitive , // TransitiveViaBot will be filled later
92+ ( false , _) => VerifiedStatus :: Unencrypted ,
93+ } ;
94+
95+ if verifier != ContactId :: UNDEFINED {
96+ verified_by_map. insert ( id, verifier) ;
97+ }
98+
99+ if bot {
100+ bot_ids. insert ( id) ;
101+ }
102+
103+ Ok ( ContactInfo {
104+ id,
105+ verified,
106+ verifier,
107+ bot,
108+ direct_chat : false , // will be filled later
109+ last_seen,
110+ transitive_chain : None , // will be filled later
111+ } )
112+ } ,
113+ |rows| {
114+ rows. collect :: < std:: result:: Result < Vec < _ > , _ > > ( )
115+ . map_err ( Into :: into)
116+ } ,
117+ )
118+ . await ?;
119+
120+ // Fill TransitiveViaBot and transitive_chain
121+ for contact in contacts. iter_mut ( ) {
122+ if contact. verified == VerifiedStatus :: Transitive {
123+ let mut transitive_chain: u32 = 0 ;
124+ let mut has_bot = false ;
125+ let mut current_verifier_id = contact. id ;
126+
127+ while current_verifier_id != ContactId :: SELF {
128+ current_verifier_id = match verified_by_map. get ( & current_verifier_id) {
129+ Some ( id) => * id,
130+ None => {
131+ // The chain ends here, probably because some verification was done
132+ // before we started recording verifiers.
133+ // It's unclear how long the chain really is.
134+ transitive_chain = 0 ;
135+ break ;
136+ }
137+ } ;
138+ if bot_ids. contains ( & current_verifier_id) {
139+ has_bot = true ;
140+ }
141+ transitive_chain = transitive_chain. saturating_add ( 1 ) ;
142+ }
143+
144+ if transitive_chain > 0 {
145+ contact. transitive_chain = Some ( transitive_chain) ;
146+ }
147+
148+ if has_bot {
149+ contact. verified = VerifiedStatus :: TransitiveViaBot ;
150+ }
151+ }
152+ }
153+
154+ // Fill direct_chat
155+ for contact in contacts. iter_mut ( ) {
156+ let direct_chat = context
157+ . sql
158+ . exists (
159+ "SELECT COUNT(*)
160+ FROM chats_contacts cc INNER JOIN chats
161+ WHERE cc.contact_id=? AND chats.type=?" ,
162+ ( contact. id , Chattype :: Single ) ,
163+ )
164+ . await ?;
165+ contact. direct_chat = direct_chat;
166+ }
167+
168+ Ok ( contacts)
169+ }
170+
39171/// Sends a message with statistics about the usage of Delta Chat,
40172/// if the last time such a message was sent
41173/// was more than a week ago.
@@ -70,7 +202,6 @@ async fn send_self_report(context: &Context) -> Result<ChatId> {
70202 . log_err ( context)
71203 . ok ( ) ;
72204
73- const SELF_REPORTING_BOT_VCARD : & str = include_str ! ( "../assets/self-reporting-bot.vcf" ) ;
74205 let contact_id: ContactId = * import_vcard ( context, SELF_REPORTING_BOT_VCARD )
75206 . await ?
76207 . first ( )
@@ -227,40 +358,11 @@ async fn get_self_report(context: &Context) -> Result<String> {
227358 key_created,
228359 chat_numbers,
229360 self_reporting_id,
361+ contact_infos : get_contact_infos ( context) . await ?,
230362 } ;
231363
232364 Ok ( serde_json:: to_string_pretty ( & statistics) ?)
233365}
234366
235367#[ cfg( test) ]
236- mod self_reporting_tests {
237- use anyhow:: Context as _;
238- use strum:: IntoEnumIterator ;
239- use tempfile:: tempdir;
240-
241- use super :: * ;
242- use crate :: chat:: { get_chat_contacts, get_chat_msgs, send_msg, set_muted, Chat , MuteDuration } ;
243- use crate :: chatlist:: Chatlist ;
244- use crate :: constants:: Chattype ;
245- use crate :: mimeparser:: SystemMessage ;
246- use crate :: receive_imf:: receive_imf;
247- use crate :: test_utils:: { get_chat_msg, TestContext } ;
248- use crate :: tools:: { create_outgoing_rfc724_mid, SystemTime } ;
249-
250- #[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
251- async fn test_draft_self_report ( ) -> Result < ( ) > {
252- let alice = TestContext :: new_alice ( ) . await ;
253-
254- let chat_id = send_self_report ( & alice) . await ?;
255- let msg = get_chat_msg ( & alice, chat_id, 0 , 2 ) . await ;
256- assert_eq ! ( msg. get_info_type( ) , SystemMessage :: ChatProtectionEnabled ) ;
257-
258- let chat = Chat :: load_from_db ( & alice, chat_id) . await ?;
259- assert ! ( chat. is_protected( ) ) ;
260-
261- let statistics_msg = get_chat_msg ( & alice, chat_id, 1 , 2 ) . await ;
262- assert_eq ! ( statistics_msg. get_filename( ) . unwrap( ) , "statistics.txt" ) ;
263-
264- Ok ( ( ) )
265- }
266- }
368+ mod self_reporting_tests;
0 commit comments