openmls/test_utils/test_framework/
client.rs1use std::{collections::HashMap, sync::RwLock};
5
6use commit_builder::CommitMessageBundle;
7use openmls_basic_credential::SignatureKeyPair;
8use openmls_traits::{
9 types::{Ciphersuite, HpkeKeyPair, SignatureScheme},
10 OpenMlsProvider as _,
11};
12use tls_codec::{Deserialize, Serialize};
13
14use super::OpenMlsRustCrypto;
15
16use crate::{
17 binary_tree::array_representation::LeafNodeIndex,
18 ciphersuite::hash_ref::KeyPackageRef,
19 credentials::*,
20 extensions::*,
21 framing::*,
22 group::*,
23 key_packages::*,
24 messages::{group_info::GroupInfo, *},
25 storage::OpenMlsProvider,
26 treesync::{
27 node::{leaf_node::Capabilities, Node},
28 LeafNode, LeafNodeParameters, RatchetTree, RatchetTreeIn,
29 },
30 versions::ProtocolVersion,
31};
32
33use super::{errors::ClientError, ActionType};
34
35#[derive(Debug)]
36pub struct Client<Provider: OpenMlsProvider> {
42 pub identity: Vec<u8>,
44 pub credentials: HashMap<Ciphersuite, CredentialWithKey>,
46 pub provider: Provider,
47 pub groups: RwLock<HashMap<GroupId, MlsGroup>>,
48}
49
50impl<Provider: OpenMlsProvider> Client<Provider> {
51 pub fn get_fresh_key_package(
55 &self,
56 ciphersuite: Ciphersuite,
57 ) -> Result<KeyPackage, ClientError<Provider::StorageError>> {
58 let credential_with_key = self
59 .credentials
60 .get(&ciphersuite)
61 .ok_or(ClientError::CiphersuiteNotSupported)?;
62 let keys = SignatureKeyPair::read(
63 self.provider.storage(),
64 credential_with_key.signature_key.as_slice(),
65 ciphersuite.signature_algorithm(),
66 )
67 .unwrap();
68
69 let key_package = KeyPackage::builder()
70 .build(
71 ciphersuite,
72 &self.provider,
73 &keys,
74 credential_with_key.clone(),
75 )
76 .unwrap();
77
78 Ok(key_package.key_package)
79 }
80
81 pub fn create_group(
85 &self,
86 mls_group_create_config: MlsGroupCreateConfig,
87 ciphersuite: Ciphersuite,
88 ) -> Result<GroupId, ClientError<Provider::StorageError>> {
89 let credential_with_key = self
90 .credentials
91 .get(&ciphersuite)
92 .ok_or(ClientError::CiphersuiteNotSupported);
93 let credential_with_key = credential_with_key?;
94 let signer = SignatureKeyPair::read(
95 self.provider.storage(),
96 credential_with_key.signature_key.as_slice(),
97 ciphersuite.signature_algorithm(),
98 )
99 .unwrap();
100
101 let group_state = MlsGroup::new(
102 &self.provider,
103 &signer,
104 &mls_group_create_config,
105 credential_with_key.clone(),
106 )?;
107 let group_id = group_state.group_id().clone();
108 self.groups
109 .write()
110 .expect("An unexpected error occurred.")
111 .insert(group_state.group_id().clone(), group_state);
112 Ok(group_id)
113 }
114
115 pub fn join_group(
120 &self,
121 mls_group_config: MlsGroupJoinConfig,
122 welcome: Welcome,
123 ratchet_tree: Option<RatchetTreeIn>,
124 ) -> Result<(), ClientError<Provider::StorageError>> {
125 let staged_join = StagedWelcome::new_from_welcome(
126 &self.provider,
127 &mls_group_config,
128 welcome,
129 ratchet_tree,
130 )?;
131 let new_group = staged_join.into_group(&self.provider)?;
132 self.groups
133 .write()
134 .expect("An unexpected error occurred.")
135 .insert(new_group.group_id().to_owned(), new_group);
136 Ok(())
137 }
138
139 pub fn receive_messages_for_group<AS: Fn(&Credential) -> bool>(
143 &self,
144 message: &ProtocolMessage,
145 sender_id: &[u8],
146 authentication_service: &AS,
147 ) -> Result<(), ClientError<Provider::StorageError>> {
148 let mut group_states = self.groups.write().expect("An unexpected error occurred.");
149 let group_id = message.group_id();
150 let group_state = group_states
151 .get_mut(group_id)
152 .ok_or(ClientError::NoMatchingGroup)?;
153 if sender_id == self.identity && message.content_type() == ContentType::Commit {
154 group_state.merge_pending_commit(&self.provider)?
155 } else {
156 if message.content_type() == ContentType::Commit {
157 group_state.clear_pending_commit(self.provider.storage())?;
159 }
160 let processed_message = group_state
162 .process_message(&self.provider, message.clone())
163 .map_err(ClientError::ProcessMessageError)?;
164
165 match processed_message.into_content() {
166 ProcessedMessageContent::ApplicationMessage(_) => {}
167 ProcessedMessageContent::ProposalMessage(staged_proposal) => {
168 group_state
169 .store_pending_proposal(self.provider.storage(), *staged_proposal)?;
170 }
171 ProcessedMessageContent::ExternalJoinProposalMessage(staged_proposal) => {
172 group_state
173 .store_pending_proposal(self.provider.storage(), *staged_proposal)?;
174 }
175 ProcessedMessageContent::StagedCommitMessage(staged_commit) => {
176 for credential in staged_commit.credentials_to_verify() {
177 if !authentication_service(credential) {
178 println!(
179 "authentication service callback denied credential {credential:?}"
180 );
181 return Err(ClientError::NoMatchingCredential);
182 }
183 }
184 group_state.merge_staged_commit(&self.provider, *staged_commit)?;
185 }
186 ProcessedMessageContent::OwnPendingCommit => {
187 group_state.merge_pending_commit(&self.provider)?;
188 }
189 ProcessedMessageContent::OwnPrivateMessage => {}
192 #[cfg(feature = "extensions-draft")]
193 ProcessedMessageContent::UnresolvedAppDataCommit(_) => {
194 unimplemented!("this test framework does not handle AppDataUpdate proposals")
195 }
196 }
197 }
198
199 Ok(())
200 }
201
202 pub fn get_members_of_group(
206 &self,
207 group_id: &GroupId,
208 ) -> Result<Vec<Member>, ClientError<Provider::StorageError>> {
209 let groups = self.groups.read().expect("An unexpected error occurred.");
210 let group = groups.get(group_id).ok_or(ClientError::NoMatchingGroup)?;
211 let members = group.members().collect();
212 Ok(members)
213 }
214
215 #[allow(clippy::type_complexity)]
221 pub fn self_update(
222 &self,
223 action_type: ActionType,
224 group_id: &GroupId,
225 leaf_node_parameters: LeafNodeParameters,
226 ) -> Result<
227 (MlsMessageOut, Option<Welcome>, Option<GroupInfo>),
228 ClientError<Provider::StorageError>,
229 > {
230 let mut groups = self.groups.write().expect("An unexpected error occurred.");
231 let group = groups
232 .get_mut(group_id)
233 .ok_or(ClientError::NoMatchingGroup)?;
234 let signature_pk = group.own_leaf().unwrap().signature_key();
237 let signer = SignatureKeyPair::read(
238 self.provider.storage(),
239 signature_pk.as_slice(),
240 group.ciphersuite().signature_algorithm(),
241 )
242 .unwrap();
243 let (msg, welcome_option, group_info) = match action_type {
244 ActionType::Commit => {
245 let bundle =
246 group.self_update(&self.provider, &signer, LeafNodeParameters::default())?;
247
248 let welcome = bundle.to_welcome_msg();
249 let (msg, _, group_info) = bundle.into_contents();
250
251 (msg, welcome, group_info)
252 }
253 ActionType::Proposal => {
254 let (msg, _) =
255 group.propose_self_update(&self.provider, &signer, leaf_node_parameters)?;
256
257 (msg, None, None)
258 }
259 };
260 Ok((
261 msg,
262 welcome_option.map(|w| w.into_welcome().expect("Unexpected message type.")),
263 group_info,
264 ))
265 }
266
267 #[allow(clippy::type_complexity)]
273 pub fn add_members(
274 &self,
275 action_type: ActionType,
276 group_id: &GroupId,
277 key_packages: &[KeyPackage],
278 ) -> Result<
279 (Vec<MlsMessageOut>, Option<Welcome>, Option<GroupInfo>),
280 ClientError<Provider::StorageError>,
281 > {
282 let mut groups = self.groups.write().expect("An unexpected error occurred.");
283 let group = groups
284 .get_mut(group_id)
285 .ok_or(ClientError::NoMatchingGroup)?;
286 let signature_pk = group.own_leaf().unwrap().signature_key();
289 let signer = SignatureKeyPair::read(
290 self.provider.storage(),
291 signature_pk.as_slice(),
292 group.ciphersuite().signature_algorithm(),
293 )
294 .unwrap();
295 let action_results = match action_type {
296 ActionType::Commit => {
297 let (messages, welcome_message, group_info) =
298 group.add_members(&self.provider, &signer, key_packages)?;
299 (
300 vec![messages],
301 Some(
302 welcome_message
303 .into_welcome()
304 .expect("Unexpected message type."),
305 ),
306 group_info,
307 )
308 }
309 ActionType::Proposal => {
310 let mut messages = Vec::new();
311 for key_package in key_packages {
312 let message = group
313 .propose_add_member(&self.provider, &signer, key_package)
314 .map(|(out, _)| out)?;
315 messages.push(message);
316 }
317 (messages, None, None)
318 }
319 };
320 Ok(action_results)
321 }
322
323 #[allow(clippy::type_complexity)]
329 pub fn remove_members(
330 &self,
331 action_type: ActionType,
332 group_id: &GroupId,
333 targets: &[LeafNodeIndex],
334 ) -> Result<
335 (Vec<MlsMessageOut>, Option<Welcome>, Option<GroupInfo>),
336 ClientError<Provider::StorageError>,
337 > {
338 let mut groups = self.groups.write().expect("An unexpected error occurred.");
339 let group = groups
340 .get_mut(group_id)
341 .ok_or(ClientError::NoMatchingGroup)?;
342 let signature_pk = group.own_leaf().unwrap().signature_key();
345 let signer = SignatureKeyPair::read(
346 self.provider.storage(),
347 signature_pk.as_slice(),
348 group.ciphersuite().signature_algorithm(),
349 )
350 .unwrap();
351 let action_results = match action_type {
352 ActionType::Commit => {
353 let (message, welcome_option, group_info) =
354 group.remove_members(&self.provider, &signer, targets)?;
355 (
356 vec![message],
357 welcome_option.map(|w| w.into_welcome().expect("Unexpected message type.")),
358 group_info,
359 )
360 }
361 ActionType::Proposal => {
362 let mut messages = Vec::new();
363 for target in targets {
364 let message = group
365 .propose_remove_member(&self.provider, &signer, *target)
366 .map(|(out, _)| out)?;
367 messages.push(message);
368 }
369 (messages, None, None)
370 }
371 };
372 Ok(action_results)
373 }
374
375 pub fn identity(&self, group_id: &GroupId) -> Option<Vec<u8>> {
377 let groups = self.groups.read().unwrap();
378 let group = groups.get(group_id).unwrap();
379 let leaf = group.own_leaf();
380 leaf.map(|l| {
381 let credential = BasicCredential::try_from(l.credential().clone()).unwrap();
382 credential.identity().to_vec()
383 })
384 }
385}