1use mls_crypto_provider::MlsCryptoProvider;
2
3use crate::{ClientId, MlsConversation, Session};
4
5pub(crate) mod ciphersuite;
6pub mod conversation;
7pub(crate) mod credential;
8mod error;
9pub(crate) mod proposal;
10pub(crate) mod session;
11
12pub use error::{Error, Result};
13pub use session::{EpochObserver, HistoryObserver};
14
15#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
16#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
17pub(crate) trait HasSessionAndCrypto: Send {
18 async fn session(&self) -> Result<Session>;
19 async fn crypto_provider(&self) -> Result<MlsCryptoProvider>;
20}
21
22#[cfg(test)]
23mod tests {
24
25 use crate::{
26 CertificateBundle, ClientIdentifier, CoreCrypto, MlsCredentialType, SessionConfig,
27 mls::Session,
28 test_utils::{x509::X509TestChain, *},
29 transaction_context::Error as TransactionError,
30 };
31
32 mod conversation_epoch {
33 use super::*;
34 use crate::mls::conversation::Conversation as _;
35
36 #[apply(all_cred_cipher)]
37 async fn can_get_newly_created_conversation_epoch(case: TestContext) {
38 let [session] = case.sessions().await;
39 let conversation = case.create_conversation([&session]).await;
40 let epoch = conversation.guard().await.epoch().await;
41 assert_eq!(epoch, 0);
42 }
43
44 #[apply(all_cred_cipher)]
45 async fn can_get_conversation_epoch(case: TestContext) {
46 let [alice, bob] = case.sessions().await;
47 Box::pin(async move {
48 let conversation = case.create_conversation([&alice, &bob]).await;
49 let epoch = conversation.guard().await.epoch().await;
50 assert_eq!(epoch, 1);
51 })
52 .await;
53 }
54
55 #[apply(all_cred_cipher)]
56 async fn conversation_not_found(case: TestContext) {
57 use crate::LeafError;
58 let [session] = case.sessions().await;
59 let id = conversation_id();
60 let err = session.transaction.conversation(&id).await.unwrap_err();
61 assert!(matches!(
62 err,
63 TransactionError::Leaf(LeafError::ConversationNotFound(i)) if i == id
64 ));
65 }
66 }
67
68 mod invariants {
69 use super::*;
70 use crate::{MlsCiphersuite, mls};
71
72 #[apply(all_cred_cipher)]
73 async fn can_create_from_valid_configuration(mut case: TestContext) {
74 let db = case.create_persistent_db().await;
75 Box::pin(async move {
76 let configuration = SessionConfig::builder()
77 .database(db)
78 .client_id("alice".into())
79 .ciphersuites([case.ciphersuite()])
80 .build()
81 .validate()
82 .unwrap();
83
84 let new_client_result = Session::try_new(configuration).await;
85 assert!(new_client_result.is_ok())
86 })
87 .await
88 }
89
90 #[macro_rules_attribute::apply(smol_macros::test)]
91 async fn client_id_should_not_be_empty() {
92 let mut case = TestContext::default();
93 let db = case.create_persistent_db().await;
94 Box::pin(async move {
95 let config_err = SessionConfig::builder()
96 .database(db)
97 .client_id("".into())
98 .ciphersuites([MlsCiphersuite::default()])
99 .build()
100 .validate()
101 .unwrap_err();
102
103 assert!(matches!(config_err, mls::Error::MalformedIdentifier("client_id")));
104 })
105 .await
106 }
107 }
108
109 #[apply(all_cred_cipher)]
110 async fn create_conversation_should_fail_when_already_exists(case: TestContext) {
111 use crate::LeafError;
112
113 let [alice] = case.sessions().await;
114 Box::pin(async move {
115 let conversation = case.create_conversation([&alice]).await;
116 let id = conversation.id().clone();
117
118 let repeat_create = alice
120 .transaction
121 .new_conversation(&id, case.credential_type, case.cfg.clone())
122 .await;
123 assert!(matches!(repeat_create.unwrap_err(), TransactionError::Leaf(LeafError::ConversationAlreadyExists(i)) if i == id));
124 })
125 .await;
126 }
127
128 #[apply(all_cred_cipher)]
129 async fn can_fetch_client_public_key(mut case: TestContext) {
130 let db = case.create_persistent_db().await;
131 Box::pin(async move {
132 let configuration = SessionConfig::builder()
133 .database(db)
134 .client_id("potato".into())
135 .ciphersuites([case.ciphersuite()])
136 .build()
137 .validate()
138 .unwrap();
139
140 let result = Session::try_new(configuration).await;
141 println!("{result:?}");
142 assert!(result.is_ok());
143 })
144 .await
145 }
146
147 #[apply(all_cred_cipher)]
148 async fn can_2_phase_init_central(mut case: TestContext) {
149 let db = case.create_persistent_db().await;
150 Box::pin(async move {
151 let x509_test_chain = X509TestChain::init_empty(case.signature_scheme());
152 let configuration = SessionConfig::builder()
153 .database(db)
154 .ciphersuites([case.ciphersuite()])
155 .build()
156 .validate()
157 .unwrap();
158
159 let client = Session::try_new(configuration).await.unwrap();
161 let cc = CoreCrypto::from(client);
162 let context = cc.new_transaction().await.unwrap();
163 x509_test_chain.register_with_central(&context).await;
164
165 assert!(!context.session().await.unwrap().is_ready().await);
166 let client_id = "alice";
168 let identifier = match case.credential_type {
169 MlsCredentialType::Basic => ClientIdentifier::Basic(client_id.into()),
170 MlsCredentialType::X509 => {
171 CertificateBundle::rand_identifier(client_id, &[x509_test_chain.find_local_intermediate_ca()])
172 }
173 };
174 context.mls_init(identifier, &[case.ciphersuite()]).await.unwrap();
175 assert!(context.session().await.unwrap().is_ready().await);
176 assert_eq!(
178 context
179 .get_or_create_client_keypackages(case.ciphersuite(), case.credential_type, 2)
180 .await
181 .unwrap()
182 .len(),
183 2
184 );
185 })
186 .await
187 }
188}