1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
//! Send operations on `SessionRunner`: key packages, app messages, and
//! ban requests.
use std::sync::{Arc, RwLock};
use crate::{
app::{
ConversationState, CreatorVote, LockExt, SessionRunner, UserError,
session::runner::send_packet,
},
core::{ConsensusPlugin, ConversationPluginsFactory, build_key_package_message},
mls_crypto::{KeyPackageBytes, MlsService},
protos::de_mls::messages::v1::{
AppMessage, BanRequest, ConversationMessage, ConversationUpdateRequest, RemoveMember,
conversation_update_request,
},
};
impl<P: ConsensusPlugin, CP: ConversationPluginsFactory> SessionRunner<P, CP> {
/// Broadcast `key_package` on this conversation's welcome subtopic so
/// the steward can invite us. The caller (typically the integrator)
/// generates the key package via [`crate::app::User::generate_key_package`] —
/// KP minting is identity-bound, not conversation-bound, so it stays
/// at the User layer.
///
/// Takes `&Arc<RwLock<Self>>` so the runner lock is released before
/// awaiting on the transport.
pub async fn send_kp_message(
arc: &Arc<RwLock<Self>>,
key_package: KeyPackageBytes,
) -> Result<(), UserError> {
let (transport, packet) = {
let s = arc.read_or_err("session")?;
let packet = build_key_package_message(&s.conversation_name, key_package, &s.app_id);
(Arc::clone(s.transport()), packet)
};
send_packet(&transport, packet)?;
Ok(())
}
/// Send a chat message. Blocked in `PendingJoin` (no keys yet),
/// `Freezing`, and `Selection` (epoch rotation in flight — the message
/// might not decrypt on peers who have already merged the next commit).
/// Governance traffic has its own gate (`check_proposal_allowed`).
///
/// Takes `&Arc<RwLock<Self>>` so the runner lock is released before
/// awaiting on the transport.
pub async fn send_app_message(
arc: &Arc<RwLock<Self>>,
message: Vec<u8>,
) -> Result<(), UserError> {
let (transport, packet) = {
let mut s = arc.write_or_err("session")?;
let state = s.handle.current_state();
if matches!(
state,
ConversationState::PendingJoin
| ConversationState::Freezing
| ConversationState::Selection
) {
return Err(UserError::ConversationBlocked(state.to_string()));
}
let app_msg: AppMessage = ConversationMessage {
message,
sender: s.identity_display.to_string(),
conversation_name: s.conversation_name.clone(),
}
.into();
let app_id = Arc::clone(&s.app_id);
let transport = Arc::clone(s.transport());
let packet = s
.handle
.expect_mls_mut()?
.build_message(&app_msg, &app_id)?;
(transport, packet)
};
send_packet(&transport, packet)?;
Ok(())
}
/// Start a `RemoveMember` consensus round targeting `ban_request.user_to_ban`.
/// The requester's click means "I want this person removed" → the
/// creator's vote is bundled as YES at submit; no banner is shown to
/// the requester.
pub async fn process_ban_request(
arc: &Arc<RwLock<Self>>,
ban_request: BanRequest,
) -> Result<(), UserError> {
{
let s = arc.read_or_err("session")?;
let state = s.handle.current_state();
if state != ConversationState::Working {
return Err(UserError::ConversationBlocked(state.to_string()));
}
}
Self::initiate_proposal(
arc,
ConversationUpdateRequest {
payload: Some(conversation_update_request::Payload::RemoveMember(
RemoveMember {
identity: ban_request.user_to_ban,
},
)),
},
CreatorVote::Yes,
)
.await?;
Ok(())
}
}