1use std::{collections::HashMap, sync::Arc};
2
3use core_crypto_keystore::{
4 Database as CryptoKeystore,
5 entities::{ProteusIdentity, ProteusSession},
6 traits::FetchFromDatabase,
7};
8use proteus_wasm::{
9 keys::{IdentityKeyPair, PreKeyBundle},
10 message::Envelope,
11 session::Session,
12};
13
14use crate::{
15 CoreCrypto, Error, KeystoreError, LeafError, ProteusError, Result,
16 group_store::{GroupStore, GroupStoreEntity, GroupStoreValue},
17};
18
19pub type SessionIdentifier = String;
21
22#[derive(Debug)]
24pub struct ProteusConversationSession {
25 pub(crate) identifier: SessionIdentifier,
26 pub(crate) session: Session<Arc<IdentityKeyPair>>,
27}
28
29impl ProteusConversationSession {
30 pub fn encrypt(&mut self, plaintext: &[u8]) -> Result<Vec<u8>> {
32 self.session
33 .encrypt(plaintext)
34 .and_then(|e| e.serialise())
35 .map_err(ProteusError::wrap("encrypting message for proteus session"))
36 .map_err(Into::into)
37 }
38
39 pub async fn decrypt(&mut self, store: &mut core_crypto_keystore::Database, ciphertext: &[u8]) -> Result<Vec<u8>> {
41 let envelope = Envelope::deserialise(ciphertext).map_err(ProteusError::wrap("deserializing envelope"))?;
42 self.session
43 .decrypt(store, &envelope)
44 .await
45 .map_err(ProteusError::wrap("decrypting message for proteus session"))
46 .map_err(Into::into)
47 }
48
49 pub fn identifier(&self) -> &str {
51 &self.identifier
52 }
53
54 pub fn fingerprint_local(&self) -> String {
56 self.session.local_identity().fingerprint()
57 }
58
59 pub fn fingerprint_remote(&self) -> String {
61 self.session.remote_identity().fingerprint()
62 }
63}
64
65#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
66#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
67impl GroupStoreEntity for ProteusConversationSession {
68 type RawStoreValue = core_crypto_keystore::entities::ProteusSession;
69 type IdentityType = Arc<proteus_wasm::keys::IdentityKeyPair>;
70
71 async fn fetch_from_id(
72 id: impl AsRef<[u8]> + Send,
73 identity: Option<Self::IdentityType>,
74 keystore: &impl FetchFromDatabase,
75 ) -> crate::Result<Option<Self>> {
76 let id = str::from_utf8(id.as_ref()).map_err(KeystoreError::wrap(
77 "converting id to string to fetch ProteusConversationSession",
78 ))?;
79 let result = keystore
80 .get_borrowed::<Self::RawStoreValue>(id)
81 .await
82 .map_err(KeystoreError::wrap("finding raw group store entity by id"))?;
83 let Some(store_value) = result else {
84 return Ok(None);
85 };
86
87 let Some(identity) = identity else {
88 return Err(crate::Error::ProteusNotInitialized);
89 };
90
91 let session = proteus_wasm::session::Session::deserialise(identity, &store_value.session)
92 .map_err(ProteusError::wrap("deserializing session"))?;
93
94 Ok(Some(Self {
95 identifier: store_value.id.clone(),
96 session,
97 }))
98 }
99}
100
101impl CoreCrypto {
102 pub async fn proteus_session(
108 &self,
109 session_id: &str,
110 ) -> Result<Option<GroupStoreValue<ProteusConversationSession>>> {
111 let mut mutex = self.proteus.lock().await;
112 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
113 proteus.session(session_id, &self.database).await
114 }
115
116 pub async fn proteus_session_exists(&self, session_id: &str) -> Result<bool> {
122 let mut mutex = self.proteus.lock().await;
123 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
124 Ok(proteus.session_exists(session_id, &self.database).await)
125 }
126
127 pub fn proteus_last_resort_prekey_id() -> u16 {
129 ProteusCentral::last_resort_prekey_id()
130 }
131
132 pub async fn proteus_fingerprint(&self) -> Result<String> {
138 let mutex = self.proteus.lock().await;
139 let proteus = mutex.as_ref().ok_or(Error::ProteusNotInitialized)?;
140 Ok(proteus.fingerprint())
141 }
142
143 pub async fn proteus_fingerprint_local(&self, session_id: &str) -> Result<String> {
149 let mut mutex = self.proteus.lock().await;
150 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
151 proteus.fingerprint_local(session_id, &self.database).await
152 }
153
154 pub async fn proteus_fingerprint_remote(&self, session_id: &str) -> Result<String> {
160 let mut mutex = self.proteus.lock().await;
161 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
162 proteus.fingerprint_remote(session_id, &self.database).await
163 }
164}
165
166#[derive(Debug)]
172pub struct ProteusCentral {
173 proteus_identity: Arc<IdentityKeyPair>,
174 proteus_sessions: GroupStore<ProteusConversationSession>,
175}
176
177impl ProteusCentral {
178 pub async fn try_new(keystore: &CryptoKeystore) -> Result<Self> {
180 let proteus_identity: Arc<IdentityKeyPair> = Arc::new(Self::load_or_create_identity(keystore).await?);
181 let proteus_sessions = Self::restore_sessions(keystore, &proteus_identity).await?;
182
183 Ok(Self {
184 proteus_identity,
185 proteus_sessions,
186 })
187 }
188
189 pub(crate) async fn reload_sessions(&mut self, keystore: &CryptoKeystore) -> Result<()> {
191 self.proteus_sessions = Self::restore_sessions(keystore, &self.proteus_identity).await?;
192 Ok(())
193 }
194
195 async fn load_or_create_identity(keystore: &CryptoKeystore) -> Result<IdentityKeyPair> {
199 let Some(identity) = keystore
200 .get_unique::<ProteusIdentity>()
201 .await
202 .map_err(KeystoreError::wrap("finding proteus identity"))?
203 else {
204 return Self::create_identity(keystore).await;
205 };
206
207 let sk = identity.sk_raw();
208 let pk = identity.pk_raw();
209
210 IdentityKeyPair::from_raw_key_pair(*sk, *pk)
212 .map_err(ProteusError::wrap("constructing identity keypair"))
213 .map_err(Into::into)
214 }
215
216 async fn create_identity(keystore: &CryptoKeystore) -> Result<IdentityKeyPair> {
218 let kp = IdentityKeyPair::new();
219 let pk = kp.public_key.public_key.as_slice().to_vec();
220
221 let ks_identity = ProteusIdentity {
222 sk: kp.secret_key.to_keypair_bytes().into(),
223 pk,
224 };
225 keystore
226 .save(ks_identity)
227 .await
228 .map_err(KeystoreError::wrap("saving new proteus identity"))?;
229
230 Ok(kp)
231 }
232
233 async fn restore_sessions(
235 keystore: &core_crypto_keystore::Database,
236 identity: &Arc<IdentityKeyPair>,
237 ) -> Result<GroupStore<ProteusConversationSession>> {
238 let mut proteus_sessions = GroupStore::new_with_limit(crate::group_store::ITEM_LIMIT * 2);
239 for session in keystore
240 .load_all::<ProteusSession>()
241 .await
242 .map_err(KeystoreError::wrap("finding all proteus sessions"))?
243 {
244 let proteus_session = Session::deserialise(identity.clone(), &session.session)
245 .map_err(ProteusError::wrap("deserializing session"))?;
246
247 let identifier = session.id.clone();
248
249 let proteus_conversation = ProteusConversationSession {
250 identifier: identifier.clone(),
251 session: proteus_session,
252 };
253
254 if proteus_sessions
255 .try_insert(identifier.into_bytes(), proteus_conversation)
256 .is_err()
257 {
258 break;
259 }
260 }
261
262 Ok(proteus_sessions)
263 }
264
265 pub async fn session_from_prekey(
267 &mut self,
268 session_id: &str,
269 key: &[u8],
270 ) -> Result<GroupStoreValue<ProteusConversationSession>> {
271 let prekey = PreKeyBundle::deserialise(key).map_err(ProteusError::wrap("deserializing prekey bundle"))?;
272 let proteus_session = Session::init_from_prekey::<core_crypto_keystore::CryptoKeystoreError>(
303 self.proteus_identity.clone(),
304 prekey,
305 )
306 .map_err(ProteusError::wrap("initializing session from prekey"))?;
307
308 let proteus_conversation = ProteusConversationSession {
309 identifier: session_id.into(),
310 session: proteus_session,
311 };
312
313 self.proteus_sessions
314 .insert(session_id.as_bytes(), proteus_conversation);
315
316 Ok(self.proteus_sessions.get(session_id.as_bytes()).unwrap().clone())
317 }
318
319 pub(crate) async fn session_from_message(
321 &mut self,
322 keystore: &mut CryptoKeystore,
323 session_id: &str,
324 envelope: &[u8],
325 ) -> Result<(GroupStoreValue<ProteusConversationSession>, Vec<u8>)> {
326 let message = Envelope::deserialise(envelope).map_err(ProteusError::wrap("deserialising envelope"))?;
327 let (session, payload) = Session::init_from_message(self.proteus_identity.clone(), keystore, &message)
328 .await
329 .map_err(ProteusError::wrap("initializing session from message"))?;
330
331 let proteus_conversation = ProteusConversationSession {
332 identifier: session_id.into(),
333 session,
334 };
335
336 self.proteus_sessions
337 .insert(session_id.as_bytes(), proteus_conversation);
338
339 Ok((
340 self.proteus_sessions.get(session_id.as_bytes()).unwrap().clone(),
341 payload,
342 ))
343 }
344
345 pub(crate) async fn session_save(&mut self, keystore: &CryptoKeystore, session_id: &str) -> Result<()> {
350 if let Some(session) = self
351 .proteus_sessions
352 .get_fetch(session_id.as_bytes(), keystore, Some(self.proteus_identity.clone()))
353 .await?
354 {
355 Self::session_save_by_ref(keystore, session).await?;
356 }
357
358 Ok(())
359 }
360
361 pub(crate) async fn session_save_by_ref(
362 keystore: &CryptoKeystore,
363 session: GroupStoreValue<ProteusConversationSession>,
364 ) -> Result<()> {
365 let session = session.read().await;
366 let db_session = ProteusSession {
367 id: session.identifier().to_string(),
368 session: session
369 .session
370 .serialise()
371 .map_err(ProteusError::wrap("serializing session"))?,
372 };
373 keystore
374 .save(db_session)
375 .await
376 .map_err(KeystoreError::wrap("saving proteus session"))?;
377 Ok(())
378 }
379
380 pub(crate) async fn session_delete(&mut self, keystore: &CryptoKeystore, session_id: &str) -> Result<()> {
382 if keystore.remove_borrowed::<ProteusSession>(session_id).await.is_ok() {
383 let _ = self.proteus_sessions.remove(session_id.as_bytes());
384 }
385 Ok(())
386 }
387
388 pub(crate) async fn session(
390 &mut self,
391 session_id: &str,
392 keystore: &CryptoKeystore,
393 ) -> Result<Option<GroupStoreValue<ProteusConversationSession>>> {
394 self.proteus_sessions
395 .get_fetch(session_id.as_bytes(), keystore, Some(self.proteus_identity.clone()))
396 .await
397 }
398
399 pub(crate) async fn session_exists(&mut self, session_id: &str, keystore: &CryptoKeystore) -> bool {
401 self.session(session_id, keystore).await.ok().flatten().is_some()
402 }
403
404 pub(crate) async fn decrypt(
407 &mut self,
408 keystore: &mut CryptoKeystore,
409 session_id: &str,
410 ciphertext: &[u8],
411 ) -> Result<Vec<u8>> {
412 let session = self
413 .proteus_sessions
414 .get_fetch(session_id.as_bytes(), keystore, Some(self.proteus_identity.clone()))
415 .await?
416 .ok_or(LeafError::ConversationNotFound(session_id.as_bytes().into()))
417 .map_err(ProteusError::wrap("getting session"))?;
418
419 let plaintext = session.write().await.decrypt(keystore, ciphertext).await?;
420 ProteusCentral::session_save_by_ref(keystore, session).await?;
421
422 Ok(plaintext)
423 }
424
425 pub(crate) async fn encrypt(
427 &mut self,
428 keystore: &mut CryptoKeystore,
429 session_id: &str,
430 plaintext: &[u8],
431 ) -> Result<Vec<u8>> {
432 let session = self
433 .session(session_id, keystore)
434 .await?
435 .ok_or(LeafError::ConversationNotFound(session_id.as_bytes().into()))
436 .map_err(ProteusError::wrap("getting session"))?;
437
438 let ciphertext = session.write().await.encrypt(plaintext)?;
439 ProteusCentral::session_save_by_ref(keystore, session).await?;
440
441 Ok(ciphertext)
442 }
443
444 pub(crate) async fn encrypt_batched(
447 &mut self,
448 keystore: &mut CryptoKeystore,
449 sessions: &[impl AsRef<str>],
450 plaintext: &[u8],
451 ) -> Result<HashMap<String, Vec<u8>>> {
452 let mut acc = HashMap::new();
453 for session_id in sessions {
454 if let Some(session) = self.session(session_id.as_ref(), keystore).await? {
455 let mut session_w = session.write().await;
456 acc.insert(session_w.identifier.clone(), session_w.encrypt(plaintext)?);
457 drop(session_w);
458
459 ProteusCentral::session_save_by_ref(keystore, session).await?;
460 }
461 }
462 Ok(acc)
463 }
464
465 pub(crate) async fn new_prekey(&self, id: u16, keystore: &CryptoKeystore) -> Result<Vec<u8>> {
468 use proteus_wasm::keys::{PreKey, PreKeyId};
469
470 let prekey_id = PreKeyId::new(id);
471 let prekey = PreKey::new(prekey_id);
472 let keystore_prekey = core_crypto_keystore::entities::ProteusPrekey::from_raw(
473 id,
474 prekey.serialise().map_err(ProteusError::wrap("serialising prekey"))?,
475 );
476 let bundle = PreKeyBundle::new(self.proteus_identity.as_ref().public_key.clone(), &prekey);
477 let bundle = bundle
478 .serialise()
479 .map_err(ProteusError::wrap("serialising prekey bundle"))?;
480 keystore
481 .save(keystore_prekey)
482 .await
483 .map_err(KeystoreError::wrap("saving keystore prekey"))?;
484 Ok(bundle)
485 }
486
487 pub(crate) async fn new_prekey_auto(&self, keystore: &CryptoKeystore) -> Result<(u16, Vec<u8>)> {
491 let id = core_crypto_keystore::entities::ProteusPrekey::get_free_id(keystore)
492 .await
493 .map_err(KeystoreError::wrap("getting proteus prekey by id"))?;
494 Ok((id, self.new_prekey(id, keystore).await?))
495 }
496
497 pub fn last_resort_prekey_id() -> u16 {
499 proteus_wasm::keys::MAX_PREKEY_ID.value()
500 }
501
502 pub(crate) async fn last_resort_prekey(&self, keystore: &CryptoKeystore) -> Result<Vec<u8>> {
505 let last_resort = if let Some(last_resort) = keystore
506 .get::<core_crypto_keystore::entities::ProteusPrekey>(&Self::last_resort_prekey_id())
507 .await
508 .map_err(KeystoreError::wrap("finding proteus prekey"))?
509 {
510 proteus_wasm::keys::PreKey::deserialise(&last_resort.prekey)
511 .map_err(ProteusError::wrap("deserialising proteus prekey"))?
512 } else {
513 let last_resort = proteus_wasm::keys::PreKey::last_resort();
514
515 use core_crypto_keystore::CryptoKeystoreProteus as _;
516 keystore
517 .proteus_store_prekey(
518 Self::last_resort_prekey_id(),
519 &last_resort
520 .serialise()
521 .map_err(ProteusError::wrap("serialising last resort prekey"))?,
522 )
523 .await
524 .map_err(KeystoreError::wrap("storing proteus prekey"))?;
525
526 last_resort
527 };
528
529 let bundle = PreKeyBundle::new(self.proteus_identity.as_ref().public_key.clone(), &last_resort);
530 let bundle = bundle
531 .serialise()
532 .map_err(ProteusError::wrap("serialising prekey bundle"))?;
533
534 Ok(bundle)
535 }
536
537 pub fn identity(&self) -> &IdentityKeyPair {
539 self.proteus_identity.as_ref()
540 }
541
542 pub fn fingerprint(&self) -> String {
544 self.proteus_identity.as_ref().public_key.fingerprint()
545 }
546
547 pub(crate) async fn fingerprint_local(&mut self, session_id: &str, keystore: &CryptoKeystore) -> Result<String> {
552 let session = self
553 .session(session_id, keystore)
554 .await?
555 .ok_or(LeafError::ConversationNotFound(session_id.as_bytes().into()))
556 .map_err(ProteusError::wrap("getting session"))?;
557 let fingerprint = session.read().await.fingerprint_local();
558 Ok(fingerprint)
559 }
560
561 pub(crate) async fn fingerprint_remote(&mut self, session_id: &str, keystore: &CryptoKeystore) -> Result<String> {
566 let session = self
567 .session(session_id, keystore)
568 .await?
569 .ok_or(LeafError::ConversationNotFound(session_id.as_bytes().into()))
570 .map_err(ProteusError::wrap("getting session"))?;
571 let fingerprint = session.read().await.fingerprint_remote();
572 Ok(fingerprint)
573 }
574
575 pub fn fingerprint_prekeybundle(prekey: &[u8]) -> Result<String> {
580 let prekey = PreKeyBundle::deserialise(prekey).map_err(ProteusError::wrap("deserialising prekey bundle"))?;
581 Ok(prekey.identity_key.fingerprint())
582 }
583}
584
585#[cfg(test)]
586mod tests {
587 use core_crypto_keystore::{ConnectionType, Database, DatabaseKey};
588
589 use super::*;
590 use crate::{
591 CertificateBundle, ClientIdentifier, CredentialType,
592 test_utils::{proteus_utils::*, x509::X509TestChain, *},
593 };
594 #[macro_rules_attribute::apply(smol_macros::test)]
595 async fn cc_can_init() {
596 #[cfg(not(target_family = "wasm"))]
597 let (path, db_file) = tmp_db_file();
598 #[cfg(target_family = "wasm")]
599 let (path, _) = tmp_db_file();
600 let db = Database::open(ConnectionType::Persistent(&path), &DatabaseKey::generate())
601 .await
602 .unwrap();
603
604 let cc: CoreCrypto = CoreCrypto::new(db);
605 let context = cc.new_transaction().await.unwrap();
606 assert!(context.proteus_init().await.is_ok());
607 assert!(context.proteus_new_prekey(1).await.is_ok());
608 context.finish().await.unwrap();
609 #[cfg(not(target_family = "wasm"))]
610 drop(db_file);
611 }
612
613 #[ignore]
616 #[apply(all_cred_cipher)]
617 async fn cc_can_2_phase_init(case: TestContext) {
618 use crate::{ClientId, Credential};
619
620 #[cfg(not(target_family = "wasm"))]
621 let (path, db_file) = tmp_db_file();
622 #[cfg(target_family = "wasm")]
623 let (path, _) = tmp_db_file();
624 let db = Database::open(ConnectionType::Persistent(&path), &DatabaseKey::generate())
625 .await
626 .unwrap();
627
628 let cc: CoreCrypto = CoreCrypto::new(db);
629 let transaction = cc.new_transaction().await.unwrap();
630 let x509_test_chain = X509TestChain::init_empty(case.signature_scheme());
631 x509_test_chain.register_with_central(&transaction).await;
632 assert!(transaction.proteus_init().await.is_ok());
633 assert!(transaction.proteus_new_prekey(1).await.is_ok());
635 let client_id = ClientId::from("alice");
637 let identifier = match case.credential_type {
638 CredentialType::Basic => ClientIdentifier::Basic(client_id),
639 CredentialType::X509 => {
640 CertificateBundle::rand_identifier(&client_id, &[x509_test_chain.find_local_intermediate_ca()])
641 }
642 };
643 let transport = Arc::new(CoreCryptoTransportSuccessProvider::default());
644 transaction
645 .mls_init(identifier.clone(), &[case.ciphersuite()], transport)
646 .await
647 .unwrap();
648 let session = &cc.mls_session().await.unwrap();
649 let credential =
650 Credential::from_identifier(&identifier, case.ciphersuite(), &session.crypto_provider).unwrap();
651 let credential_ref = session.add_credential(credential).await.unwrap();
652
653 assert!(transaction.generate_keypackage(&credential_ref, None).await.is_ok());
655
656 #[cfg(not(target_family = "wasm"))]
657 drop(db_file);
658 }
659
660 #[macro_rules_attribute::apply(smol_macros::test)]
661 async fn can_init() {
662 #[cfg(not(target_family = "wasm"))]
663 let (path, db_file) = tmp_db_file();
664 #[cfg(target_family = "wasm")]
665 let (path, _) = tmp_db_file();
666 let key = DatabaseKey::generate();
667 let keystore = core_crypto_keystore::Database::open(ConnectionType::Persistent(&path), &key)
668 .await
669 .unwrap();
670 keystore.new_transaction().await.unwrap();
671 let central = ProteusCentral::try_new(&keystore).await.unwrap();
672 let identity = (*central.proteus_identity).clone();
673 keystore.commit_transaction().await.unwrap();
674
675 let keystore = core_crypto_keystore::Database::open(ConnectionType::Persistent(&path), &key)
676 .await
677 .unwrap();
678 keystore.new_transaction().await.unwrap();
679 let central = ProteusCentral::try_new(&keystore).await.unwrap();
680 keystore.commit_transaction().await.unwrap();
681 assert_eq!(identity, *central.proteus_identity);
682
683 keystore.wipe().await.unwrap();
684 #[cfg(not(target_family = "wasm"))]
685 drop(db_file);
686 }
687
688 #[macro_rules_attribute::apply(smol_macros::test)]
689 async fn can_talk_with_proteus() {
690 #[cfg(not(target_family = "wasm"))]
691 let (path, db_file) = tmp_db_file();
692 #[cfg(target_family = "wasm")]
693 let (path, _) = tmp_db_file();
694
695 let session_id = uuid::Uuid::new_v4().hyphenated().to_string();
696
697 let key = DatabaseKey::generate();
698 let mut keystore = core_crypto_keystore::Database::open(ConnectionType::Persistent(&path), &key)
699 .await
700 .unwrap();
701 keystore.new_transaction().await.unwrap();
702
703 let mut alice = ProteusCentral::try_new(&keystore).await.unwrap();
704
705 let mut bob = CryptoboxLike::init();
706 let bob_pk_bundle = bob.new_prekey();
707
708 alice
709 .session_from_prekey(&session_id, &bob_pk_bundle.serialise().unwrap())
710 .await
711 .unwrap();
712
713 let message = b"Hello world";
714
715 let encrypted = alice.encrypt(&mut keystore, &session_id, message).await.unwrap();
716 let decrypted = bob.decrypt(&session_id, &encrypted).await;
717 assert_eq!(decrypted, message);
718
719 let encrypted = bob.encrypt(&session_id, message);
720 let decrypted = alice.decrypt(&mut keystore, &session_id, &encrypted).await.unwrap();
721 assert_eq!(decrypted, message);
722
723 keystore.commit_transaction().await.unwrap();
724 keystore.wipe().await.unwrap();
725 #[cfg(not(target_family = "wasm"))]
726 drop(db_file);
727 }
728
729 #[macro_rules_attribute::apply(smol_macros::test)]
730 async fn can_produce_proteus_consumed_prekeys() {
731 #[cfg(not(target_family = "wasm"))]
732 let (path, db_file) = tmp_db_file();
733 #[cfg(target_family = "wasm")]
734 let (path, _) = tmp_db_file();
735
736 let session_id = uuid::Uuid::new_v4().hyphenated().to_string();
737
738 let key = DatabaseKey::generate();
739 let mut keystore = core_crypto_keystore::Database::open(ConnectionType::Persistent(&path), &key)
740 .await
741 .unwrap();
742 keystore.new_transaction().await.unwrap();
743 let mut alice = ProteusCentral::try_new(&keystore).await.unwrap();
744
745 let mut bob = CryptoboxLike::init();
746
747 let alice_prekey_bundle_ser = alice.new_prekey(1, &keystore).await.unwrap();
748
749 bob.init_session_from_prekey_bundle(&session_id, &alice_prekey_bundle_ser);
750 let message = b"Hello world!";
751 let encrypted = bob.encrypt(&session_id, message);
752
753 let (_, decrypted) = alice
754 .session_from_message(&mut keystore, &session_id, &encrypted)
755 .await
756 .unwrap();
757
758 assert_eq!(message, decrypted.as_slice());
759
760 let encrypted = alice.encrypt(&mut keystore, &session_id, message).await.unwrap();
761 let decrypted = bob.decrypt(&session_id, &encrypted).await;
762
763 assert_eq!(message, decrypted.as_slice());
764 keystore.commit_transaction().await.unwrap();
765 keystore.wipe().await.unwrap();
766 #[cfg(not(target_family = "wasm"))]
767 drop(db_file);
768 }
769
770 #[macro_rules_attribute::apply(smol_macros::test)]
771 async fn auto_prekeys_are_sequential() {
772 use core_crypto_keystore::entities::ProteusPrekey;
773 const GAP_AMOUNT: u16 = 5;
774 const ID_TEST_RANGE: std::ops::RangeInclusive<u16> = 1..=30;
775
776 #[cfg(not(target_family = "wasm"))]
777 let (path, db_file) = tmp_db_file();
778 #[cfg(target_family = "wasm")]
779 let (path, _) = tmp_db_file();
780
781 let key = DatabaseKey::generate();
782 let keystore = core_crypto_keystore::Database::open(ConnectionType::Persistent(&path), &key)
783 .await
784 .unwrap();
785 keystore.new_transaction().await.unwrap();
786 let alice = ProteusCentral::try_new(&keystore).await.unwrap();
787
788 for i in ID_TEST_RANGE {
789 let (pk_id, pkb) = alice.new_prekey_auto(&keystore).await.unwrap();
790 assert_eq!(i, pk_id);
791 let prekey = proteus_wasm::keys::PreKeyBundle::deserialise(&pkb).unwrap();
792 assert_eq!(prekey.prekey_id.value(), pk_id);
793 }
794
795 use rand::Rng as _;
796 let mut rng = rand::thread_rng();
797 let mut gap_ids: Vec<u16> = (0..GAP_AMOUNT).map(|_| rng.gen_range(ID_TEST_RANGE)).collect();
798 gap_ids.sort();
799 gap_ids.dedup();
800 while gap_ids.len() < GAP_AMOUNT as usize {
801 gap_ids.push(rng.gen_range(ID_TEST_RANGE));
802 gap_ids.sort();
803 gap_ids.dedup();
804 }
805 for gap_id in gap_ids.iter() {
806 keystore.remove::<ProteusPrekey>(gap_id).await.unwrap();
807 }
808
809 gap_ids.sort();
810
811 for gap_id in gap_ids.iter() {
812 let (pk_id, pkb) = alice.new_prekey_auto(&keystore).await.unwrap();
813 assert_eq!(pk_id, *gap_id);
814 let prekey = proteus_wasm::keys::PreKeyBundle::deserialise(&pkb).unwrap();
815 assert_eq!(prekey.prekey_id.value(), *gap_id);
816 }
817
818 let mut gap_ids: Vec<u16> = (0..GAP_AMOUNT).map(|_| rng.gen_range(ID_TEST_RANGE)).collect();
819 gap_ids.sort();
820 gap_ids.dedup();
821 while gap_ids.len() < GAP_AMOUNT as usize {
822 gap_ids.push(rng.gen_range(ID_TEST_RANGE));
823 gap_ids.sort();
824 gap_ids.dedup();
825 }
826 for gap_id in gap_ids.iter() {
827 keystore.remove::<ProteusPrekey>(gap_id).await.unwrap();
828 }
829
830 let potential_range = *ID_TEST_RANGE.end()..=(*ID_TEST_RANGE.end() * 2);
831 let potential_range_check = potential_range.clone();
832 for _ in potential_range {
833 let (pk_id, pkb) = alice.new_prekey_auto(&keystore).await.unwrap();
834 assert!(gap_ids.contains(&pk_id) || potential_range_check.contains(&pk_id));
835 let prekey = proteus_wasm::keys::PreKeyBundle::deserialise(&pkb).unwrap();
836 assert_eq!(prekey.prekey_id.value(), pk_id);
837 }
838 keystore.commit_transaction().await.unwrap();
839 keystore.wipe().await.unwrap();
840 #[cfg(not(target_family = "wasm"))]
841 drop(db_file);
842 }
843}