1#![cfg_attr(target_family = "wasm", allow(dead_code, unused_imports))]
18
19#[cfg(not(target_family = "wasm"))]
20use crate::clients::{EmulatedClient, EmulatedProteusClient};
21#[cfg(not(target_family = "wasm"))]
22use crate::util::{MlsTransportSuccessProvider, MlsTransportTestExt};
23use color_eyre::eyre::{Result, eyre};
24use core_crypto::prelude::CiphersuiteName;
25use std::rc::Rc;
26use std::sync::Arc;
27use tls_codec::Serialize;
28
29#[cfg(not(target_family = "wasm"))]
30mod build;
31#[cfg(not(target_family = "wasm"))]
32mod clients;
33#[cfg(not(target_family = "wasm"))]
34mod util;
35
36#[cfg(not(target_family = "wasm"))]
37const TEST_SERVER_PORT: &str = "8000";
38#[cfg(not(target_family = "wasm"))]
39const TEST_SERVER_URI: &str = const_format::concatcp!("http://localhost:", TEST_SERVER_PORT);
40
41const MLS_MAIN_CLIENTID: &[u8] = b"test_main";
42const MLS_CONVERSATION_ID: &[u8] = b"test_conversation";
43const ROUNDTRIP_MSG_AMOUNT: usize = 100;
44
45const CIPHERSUITE_IN_USE: CiphersuiteName = CiphersuiteName::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519;
46
47fn main() -> Result<()> {
50 run_test()
51}
52
53#[cfg(not(target_family = "wasm"))]
56fn run_test() -> Result<()> {
57 use std::time::{Duration, Instant};
58
59 use tokio::net::{TcpListener, TcpStream};
60
61 color_eyre::install()?;
62 if std::env::var("RUST_LOG").is_ok() || std::env::var("CI").is_ok() {
63 femme::start();
64 }
65
66 let force_webdriver_install = std::env::var("FORCE_WEBDRIVER_INSTALL").is_ok();
67
68 let current_dir = std::env::current_dir()?;
70 if current_dir.ends_with("interop") {
71 let new_cwd = current_dir.parent().unwrap();
72 log::info!("cwd was {current_dir:?}; setting it to {new_cwd:?}");
73 std::env::set_current_dir(new_cwd)?;
74 }
75 let tempdir = tempfile::tempdir()?;
76
77 let runtime = tokio::runtime::Builder::new_multi_thread()
79 .enable_all()
80 .build()
81 .unwrap();
82
83 runtime.block_on(async {
84 build::web::webdriver::setup_webdriver(force_webdriver_install).await?;
85
86 build::web::wasm::build_wasm(tempdir.path().to_path_buf()).await?;
87
88 let spinner = util::RunningProcess::new("Starting HTTP server...", false);
89 let http_server_hwnd = tokio::task::spawn(build::web::wasm::spawn_http_server(tempdir.path().to_path_buf()));
90 spinner.success(format!("HTTP server started at 0.0.0.0:{TEST_SERVER_PORT} [OK]"));
91
92 let mut spinner = util::RunningProcess::new("Starting WebDriver [ChromeDriver & GeckoDriver]...", false);
93 let chrome_driver_addr = TcpListener::bind("127.0.0.1:0").await?.local_addr()?;
94 let mut chrome_webdriver = build::web::webdriver::start_webdriver_chrome(&chrome_driver_addr).await?;
95 spinner.update("Sleeping to wait for Webdrivers to get ready...");
96 let timeout = Duration::from_secs(5);
97 let start = Instant::now();
98 while start.elapsed() < timeout {
99 let chrome_ready = TcpStream::connect(&chrome_driver_addr).await.is_ok();
100 if chrome_ready {
101 break;
102 }
103 tokio::time::sleep(Duration::from_millis(100)).await;
104 }
105 spinner.success("WebDriver [OK]");
106
107 run_mls_test(&chrome_driver_addr).await?;
108
109 #[cfg(feature = "proteus")]
110 run_proteus_test(&chrome_driver_addr).await?;
111
112 chrome_webdriver.kill().await?;
113 http_server_hwnd.abort();
114 Ok(())
115 })
116}
117
118#[cfg(target_family = "wasm")]
119fn run_test() -> Result<()> {
120 panic!("E2E tests cannot be run on WASM")
121}
122
123#[cfg(not(target_family = "wasm"))]
124async fn run_mls_test(chrome_driver_addr: &std::net::SocketAddr) -> Result<()> {
125 use core_crypto::prelude::*;
126 use rand::distributions::DistString;
127
128 log::info!("Using ciphersuite {}", CIPHERSUITE_IN_USE);
129
130 let spinner = util::RunningProcess::new("[MLS] Step 0: Initializing clients & env...", true);
131
132 let ios_client = Rc::new(clients::corecrypto::ios::CoreCryptoIosClient::new().await?);
133 let native_client = Rc::new(clients::corecrypto::native::CoreCryptoNativeClient::new().await?);
134 let ffi_client = Rc::new(clients::corecrypto::ffi::CoreCryptoFfiClient::new().await?);
135 let web_client = Rc::new(clients::corecrypto::web::CoreCryptoWebClient::new(chrome_driver_addr).await?);
136
137 let mut clients: Vec<Rc<dyn clients::EmulatedMlsClient>> = vec![
138 native_client.clone(),
139 ffi_client.clone(),
140 web_client.clone(),
141 ios_client.clone(),
142 ];
143
144 let ciphersuites = vec![CIPHERSUITE_IN_USE.into()];
145 let configuration = MlsCentralConfiguration::try_new(
146 "whatever".into(),
147 "test".into(),
148 Some(MLS_MAIN_CLIENTID.into()),
149 ciphersuites,
150 None,
151 Some(100),
152 )?;
153 let master_client = MlsCentral::try_new_in_memory(configuration).await?;
154
155 let conversation_id = MLS_CONVERSATION_ID.to_vec();
156 let config = MlsConversationConfiguration {
157 ciphersuite: CIPHERSUITE_IN_USE.into(),
158 ..Default::default()
159 };
160 let cc = CoreCrypto::from(master_client.clone());
161
162 let success_provider = Arc::new(MlsTransportSuccessProvider::default());
163
164 cc.provide_transport(success_provider.clone()).await;
165 let transaction = cc.new_transaction().await?;
166 transaction
167 .new_conversation(&conversation_id, MlsCredentialType::Basic, config)
168 .await?;
169
170 spinner.success("[MLS] Step 0: Initializing clients [OK]");
171
172 let spinner = util::RunningProcess::new("[MLS] Step 1: Fetching KeyPackages from clients...", true);
173
174 use tls_codec::Deserialize as _;
175 let mut key_packages = vec![];
176 for c in clients.iter_mut() {
177 let kp = c.get_keypackage().await?;
178 let kp = KeyPackageIn::tls_deserialize(&mut kp.as_slice())?;
179 key_packages.push(kp);
180 }
181
182 spinner.success("[MLS] Step 1: KeyPackages [OK]");
183
184 let spinner = util::RunningProcess::new("[MLS] Step 2: Adding clients to conversation...", true);
185
186 transaction
187 .conversation(&conversation_id)
188 .await?
189 .add_members(key_packages)
190 .await?;
191
192 let conversation_add_msg = success_provider.latest_welcome_message().await;
193 let welcome_raw = conversation_add_msg.tls_serialize_detached()?;
194
195 for c in clients.iter_mut() {
196 let conversation_id_from_welcome = c.process_welcome(&welcome_raw).await?;
197 assert_eq!(conversation_id_from_welcome, conversation_id);
198 }
199
200 spinner.success("[MLS] Step 2: Added clients [OK]");
201
202 let mut spinner = util::RunningProcess::new(
203 format!("[MLS] Step 3: Roundtripping messages [0/{ROUNDTRIP_MSG_AMOUNT}]"),
204 true,
205 );
206
207 let mut prng = rand::thread_rng();
208 let mut message;
209 for i in 1..=ROUNDTRIP_MSG_AMOUNT {
210 message = rand::distributions::Alphanumeric.sample_string(&mut prng, 16);
211
212 log::info!(
213 "Master client [{}] >>> {}",
214 hex::encode(master_client.client_id().await?.as_slice()),
215 message
216 );
217
218 let mut message_to_decrypt = transaction
219 .conversation(&conversation_id)
220 .await?
221 .encrypt_message(&message)
222 .await?;
223
224 for c in clients.iter_mut() {
225 let decrypted_message_raw = c
226 .decrypt_message(&conversation_id, &message_to_decrypt)
227 .await?
228 .ok_or_else(|| {
229 eyre!(
230 "[MLS] No message, something went very wrong [Client = {}]",
231 c.client_type()
232 )
233 })?;
234
235 let decrypted_message = String::from_utf8(decrypted_message_raw)?;
236
237 log::info!(
238 "{} [{}] <<< {}",
239 c.client_name(),
240 hex::encode(c.client_id()),
241 decrypted_message
242 );
243
244 assert_eq!(
245 decrypted_message,
246 message,
247 "[MLS] Messages differ [Client = {}]",
248 c.client_type()
249 );
250
251 message_to_decrypt = c
252 .encrypt_message(&conversation_id, decrypted_message.as_bytes())
253 .await?;
254 }
255
256 let decrypted_master_raw = transaction
257 .conversation(&conversation_id)
258 .await
259 .unwrap()
260 .decrypt_message(message_to_decrypt)
261 .await?
262 .app_msg
263 .ok_or_else(|| eyre!("[MLS] No message received on master client"))?;
264
265 let decrypted_master = String::from_utf8(decrypted_master_raw)?;
266
267 assert_eq!(decrypted_master, message);
268
269 spinner.update(format!(
270 "[MLS] Step 3: Roundtripping messages... [{i}/{ROUNDTRIP_MSG_AMOUNT}]"
271 ));
272 }
273
274 clients.clear();
275
276 spinner.success(format!(
277 "[MLS] Step 3: Roundtripping {ROUNDTRIP_MSG_AMOUNT} messages... [OK]"
278 ));
279
280 let spinner = util::RunningProcess::new("[MLS] Step 4: Deleting clients...", true);
281
282 Rc::into_inner(ios_client)
283 .expect("Only one strong reference to the interop client")
284 .wipe()
285 .await?;
286 Rc::into_inner(native_client)
287 .expect("Only one strong reference to the interop client")
288 .wipe()
289 .await?;
290 Rc::into_inner(ffi_client)
291 .expect("Only one strong reference to the interop client")
292 .wipe()
293 .await?;
294 Rc::into_inner(web_client)
295 .expect("Only one strong reference to the interop client")
296 .wipe()
297 .await?;
298
299 spinner.success("[MLS] Step 4: Deleting clients [OK]");
300
301 Ok(())
302}
303
304#[cfg(all(not(target_family = "wasm"), feature = "proteus"))]
305async fn run_proteus_test(chrome_driver_addr: &std::net::SocketAddr) -> Result<()> {
306 use core_crypto::prelude::*;
307
308 let spinner = util::RunningProcess::new("[Proteus] Step 0: Initializing clients & env...", true);
309
310 let mut ios_client = clients::corecrypto::ios::CoreCryptoIosClient::new().await?;
311 let mut native_client = clients::corecrypto::native::CoreCryptoNativeClient::new().await?;
312 let mut ffi_client = clients::corecrypto::ffi::CoreCryptoFfiClient::new().await?;
313 let mut web_client = clients::corecrypto::web::CoreCryptoWebClient::new(chrome_driver_addr).await?;
314 let mut cryptobox_native_client = clients::cryptobox::native::CryptoboxNativeClient::new();
315 let mut cryptobox_web_client = clients::cryptobox::web::CryptoboxWebClient::new(chrome_driver_addr).await?;
316
317 ios_client.init().await?;
318 native_client.init().await?;
319 ffi_client.init().await?;
320 web_client.init().await?;
321 cryptobox_native_client.init().await?;
322 cryptobox_web_client.init().await?;
323
324 let ios_client = Rc::new(ios_client);
325 let native_client = Rc::new(native_client);
326 let ffi_client = Rc::new(ffi_client);
327 let web_client = Rc::new(web_client);
328 let cryptobox_native_client = Rc::new(cryptobox_native_client);
329 let cryptobox_web_client = Rc::new(cryptobox_web_client);
330
331 let mut clients: Vec<Rc<dyn clients::EmulatedProteusClient>> = vec![
332 ios_client.clone(),
333 native_client.clone(),
334 ffi_client.clone(),
335 web_client.clone(),
336 cryptobox_native_client.clone(),
337 cryptobox_web_client.clone(),
338 ];
339
340 let configuration = MlsCentralConfiguration::try_new(
341 "whatever".into(),
342 "test".into(),
343 Some(MLS_MAIN_CLIENTID.into()),
344 vec![MlsCiphersuite::default()],
345 None,
346 Some(100),
347 )?;
348 let master_client = CoreCrypto::from(MlsCentral::try_new_in_memory(configuration).await?);
349 let transaction = master_client.new_transaction().await?;
350 transaction.proteus_init().await?;
351
352 let master_fingerprint = master_client.proteus_fingerprint().await?;
353
354 spinner.success("[Proteus] Step 0: Initializing clients [OK]");
355
356 let mut spinner = util::RunningProcess::new("[Proteus] Step 1: Fetching PreKeys from clients...", true);
357
358 let mut client_type_mapping = std::collections::HashMap::new();
359 let mut prekeys = std::collections::HashMap::new();
360 for c in clients.iter_mut() {
361 let client_name = c.client_name();
362 spinner.update(format!("[Proteus] Step 1: PreKeys - {client_name}"));
363 let fingerprint = c.fingerprint().await?;
364 spinner.update(format!("[Proteus] Step 1: PreKeys - {fingerprint}@{client_name}"));
365 client_type_mapping.insert(fingerprint.clone(), client_name.to_string());
366 let prekey = c.get_prekey().await?;
367 prekeys.insert(fingerprint, prekey);
368 }
369
370 spinner.success("[Proteus] Step 1: PreKeys [OK]");
371
372 let mut spinner = util::RunningProcess::new("[Proteus] Step 2: Creating sessions...", true);
373
374 let mut master_sessions = vec![];
375 let mut messages = std::collections::HashMap::new();
376 const PROTEUS_INITIAL_MESSAGE: &[u8] = b"Hello world!";
377 for (fingerprint, prekey) in prekeys {
378 spinner.update(format!(
379 "[Proteus] Step 2: Session master -> {fingerprint}@{}",
380 client_type_mapping[&fingerprint]
381 ));
382 let session_arc = transaction.proteus_session_from_prekey(&fingerprint, &prekey).await?;
383 let mut session = session_arc.write().await;
384 messages.insert(fingerprint, session.encrypt(PROTEUS_INITIAL_MESSAGE)?);
385 master_sessions.push(session.identifier().to_string());
386 }
387
388 let session_id_with_master = format!("session-{master_fingerprint}");
389 for c in clients.iter_mut() {
390 let fingerprint = c.fingerprint().await?;
391 spinner.update(format!(
392 "[Proteus] Step 2: Session {fingerprint}@{} -> master",
393 c.client_name()
394 ));
395 let message = messages.remove(&fingerprint).unwrap();
396 let message_recv = c.session_from_message(&session_id_with_master, &message).await?;
397 assert_eq!(PROTEUS_INITIAL_MESSAGE, message_recv);
398 }
399
400 assert!(messages.is_empty());
401
402 spinner.success("[Proteus] Step 2: Creating sessions [OK]");
403
404 let mut spinner = util::RunningProcess::new(
405 format!("[Proteus] Step 3: Roundtripping messages [0/{ROUNDTRIP_MSG_AMOUNT}]"),
406 true,
407 );
408
409 let mut prng = rand::thread_rng();
410 let mut message = [0u8; 128];
411 let mut master_messages_to_decrypt = std::collections::HashMap::new();
412 for i in 0..ROUNDTRIP_MSG_AMOUNT {
413 use rand::RngCore as _;
414
415 prng.fill_bytes(&mut message);
416
417 let mut messages_to_decrypt = transaction.proteus_encrypt_batched(&master_sessions, &message).await?;
418
419 for c in clients.iter_mut() {
420 let fingerprint = c.fingerprint().await?;
421 let decrypted_message = c
422 .decrypt(
423 &session_id_with_master,
424 &messages_to_decrypt.remove(&fingerprint).unwrap(),
425 )
426 .await?;
427
428 assert_eq!(
429 decrypted_message,
430 message,
431 "[Proteus] Messages differ [Client = {}::{}]",
432 c.client_name(),
433 c.client_type(),
434 );
435
436 let ciphertext = c.encrypt(&session_id_with_master, &decrypted_message).await?;
437 master_messages_to_decrypt.insert(fingerprint, ciphertext);
438 }
439
440 for (fingerprint, encrypted) in master_messages_to_decrypt.drain() {
441 let decrypted = transaction.proteus_decrypt(&fingerprint, &encrypted).await?;
442 assert_eq!(decrypted, message);
443 }
444
445 spinner.update(format!(
446 "[Proteus] Step 3: Roundtripping messages... [{i}/{ROUNDTRIP_MSG_AMOUNT}]"
447 ));
448 }
449
450 clients.clear();
451
452 spinner.success(format!(
453 "[Proteus] Step 3: Roundtripping {ROUNDTRIP_MSG_AMOUNT} messages... [OK]"
454 ));
455
456 let spinner = util::RunningProcess::new("[Proteus] Step 4: Deleting clients...", true);
457
458 Rc::into_inner(ios_client)
459 .expect("Only one strong reference to the interop client")
460 .wipe()
461 .await?;
462 Rc::into_inner(native_client)
463 .expect("Only one strong reference to the interop client")
464 .wipe()
465 .await?;
466 Rc::into_inner(ffi_client)
467 .expect("Only one strong reference to the interop client")
468 .wipe()
469 .await?;
470 Rc::into_inner(web_client)
471 .expect("Only one strong reference to the interop client")
472 .wipe()
473 .await?;
474 Rc::into_inner(cryptobox_native_client)
475 .expect("Only one strong reference to the interop client")
476 .wipe()
477 .await?;
478 Rc::into_inner(cryptobox_web_client)
479 .expect("Only one strong reference to the interop client")
480 .wipe()
481 .await?;
482
483 spinner.success("[Proteus] Step 4: Deleting clients [OK]");
484
485 Ok(())
486}