Skip to main content

de_mls/app/session/
messaging.rs

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