use std::error::Error as StdError;
use openmls_traits::signatures::Signer;
use openmls_traits::{OpenMlsProvider, storage::StorageProvider};
use prost::Message;
use tracing::info;
use crate::{
ConsensusPlugin, Conversation, ConversationError, ConversationState, CreatorVote,
PeerScoringPlugin, StewardListPlugin,
mls_crypto::{KeyPackageBytes, MlsService, key_package_bytes_from_tls},
protos::de_mls::messages::v1::{
AppMessage, ConversationMessage, ConversationUpdateRequest, MemberInvite,
},
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Outbound {
pub conversation_id: String,
pub sender: Vec<u8>,
pub payload: Vec<u8>,
}
impl<C, Sc, St> Conversation<C, Sc, St>
where
C: ConsensusPlugin,
Sc: PeerScoringPlugin,
St: StewardListPlugin,
{
pub fn send_message<Pr>(
&mut self,
provider: &Pr,
message: Vec<u8>,
signer: &impl Signer,
) -> Result<(), ConversationError>
where
Pr: OpenMlsProvider,
<Pr::StorageProvider as StorageProvider<1>>::Error: StdError + Send + Sync + 'static,
{
let state = self.current_state();
if matches!(
state,
ConversationState::Freezing | ConversationState::Selection
) {
return Err(ConversationError::ConversationBlocked(state.to_string()));
}
let app_msg: AppMessage = ConversationMessage {
message,
sender: self.self_member_id.to_vec(),
conversation_id: self.conversation_id.clone(),
..Default::default()
}
.into();
let payload = self.mls_mut().build_message(provider, signer, &app_msg)?;
self.broadcast(payload);
Ok(())
}
pub fn add_member<Pr>(
&mut self,
provider: &Pr,
key_package_bytes: &[u8],
signer: &impl Signer,
) -> Result<(), ConversationError>
where
Pr: OpenMlsProvider,
<Pr::StorageProvider as StorageProvider<1>>::Error: StdError + Send + Sync + 'static,
{
let state = self.current_state();
if state != ConversationState::Working {
return Err(ConversationError::ConversationBlocked(state.to_string()));
}
self.propose_add(provider, key_package_bytes, CreatorVote::Yes, signer)
}
pub fn sponsor_member<Pr>(
&mut self,
provider: &Pr,
key_package_bytes: &[u8],
signer: &impl Signer,
) -> Result<(), ConversationError>
where
Pr: OpenMlsProvider,
<Pr::StorageProvider as StorageProvider<1>>::Error: StdError + Send + Sync + 'static,
{
if self.current_state() != ConversationState::Working {
return Ok(());
}
if self.is_epoch_steward()? {
return self.propose_add(provider, key_package_bytes, CreatorVote::Deferred, signer);
}
self.buffer_announced_add(key_package_bytes)
}
fn buffer_announced_add(&mut self, key_package_bytes: &[u8]) -> Result<(), ConversationError> {
let (kp_bytes, member_id) = key_package_bytes_from_tls(key_package_bytes.to_vec())?;
if member_id == *self.member_id_bytes() || self.mls().is_member(&member_id) {
return Ok(());
}
let epoch = self.mls().current_epoch()?;
self.queues.insert_pending_update(
ConversationUpdateRequest::member_invite(MemberInvite {
key_package_bytes: kp_bytes,
member_id,
}),
epoch,
);
Ok(())
}
fn propose_add<Pr>(
&mut self,
provider: &Pr,
key_package_bytes: &[u8],
creator_vote: CreatorVote,
signer: &impl Signer,
) -> Result<(), ConversationError>
where
Pr: OpenMlsProvider,
<Pr::StorageProvider as StorageProvider<1>>::Error: StdError + Send + Sync + 'static,
{
let (kp_bytes, member_id) = key_package_bytes_from_tls(key_package_bytes.to_vec())?;
if member_id == *self.member_id_bytes() {
return Ok(());
}
if self.mls().is_member(&member_id) {
info!(
conversation = %self.id(),
member = ?member_id,
"add member skipped: already a member"
);
return Ok(());
}
self.initiate_proposal(
provider,
ConversationUpdateRequest::member_invite(MemberInvite {
key_package_bytes: kp_bytes,
member_id,
}),
creator_vote,
signer,
)?;
Ok(())
}
pub fn remove_member<Pr>(
&mut self,
provider: &Pr,
member_id: &[u8],
signer: &impl Signer,
) -> Result<(), ConversationError>
where
Pr: OpenMlsProvider,
<Pr::StorageProvider as StorageProvider<1>>::Error: StdError + Send + Sync + 'static,
{
let state = self.current_state();
if state != ConversationState::Working {
return Err(ConversationError::ConversationBlocked(state.to_string()));
}
self.initiate_proposal(
provider,
ConversationUpdateRequest::remove_member(member_id.to_vec()),
CreatorVote::Yes,
signer,
)?;
Ok(())
}
}
pub fn build_key_package_announcement(key_package: &KeyPackageBytes) -> Vec<u8> {
MemberInvite {
key_package_bytes: key_package.as_bytes().to_vec(),
member_id: key_package.member_id().to_vec(),
}
.encode_to_vec()
}