1
1
//! TODO doc comment
2
2
3
+ use std:: collections:: { BTreeMap , BTreeSet } ;
4
+
3
5
use anyhow:: { Context as _, Result } ;
4
6
use pgp:: types:: PublicKeyTrait ;
5
7
use serde:: Serialize ;
6
8
7
9
use crate :: chat:: { self , ChatId , ChatVisibility , MuteDuration , ProtectionStatus } ;
8
10
use 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 } ;
11
13
use crate :: context:: { get_version_str, Context } ;
12
14
use crate :: download:: DownloadState ;
13
15
use crate :: key:: load_self_public_key;
@@ -16,6 +18,9 @@ use crate::message::{Message, Viewtype};
16
18
use crate :: param:: { Param , Params } ;
17
19
use crate :: tools:: { create_id, time} ;
18
20
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
+
19
24
#[ derive( Serialize ) ]
20
25
struct Statistics {
21
26
core_version : String ,
@@ -25,6 +30,7 @@ struct Statistics {
25
30
key_created : i64 ,
26
31
chat_numbers : ChatNumbers ,
27
32
self_reporting_id : String ,
33
+ contact_infos : Vec < ContactInfo > ,
28
34
}
29
35
#[ derive( Default , Serialize ) ]
30
36
struct ChatNumbers {
@@ -36,6 +42,132 @@ struct ChatNumbers {
36
42
unencrypted_mua : u32 ,
37
43
}
38
44
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
+
39
171
/// Sends a message with statistics about the usage of Delta Chat,
40
172
/// if the last time such a message was sent
41
173
/// was more than a week ago.
@@ -70,7 +202,6 @@ async fn send_self_report(context: &Context) -> Result<ChatId> {
70
202
. log_err ( context)
71
203
. ok ( ) ;
72
204
73
- const SELF_REPORTING_BOT_VCARD : & str = include_str ! ( "../assets/self-reporting-bot.vcf" ) ;
74
205
let contact_id: ContactId = * import_vcard ( context, SELF_REPORTING_BOT_VCARD )
75
206
. await ?
76
207
. first ( )
@@ -227,40 +358,11 @@ async fn get_self_report(context: &Context) -> Result<String> {
227
358
key_created,
228
359
chat_numbers,
229
360
self_reporting_id,
361
+ contact_infos : get_contact_infos ( context) . await ?,
230
362
} ;
231
363
232
364
Ok ( serde_json:: to_string_pretty ( & statistics) ?)
233
365
}
234
366
235
367
#[ 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