1use super::*;
4
5impl<Context> MlsGroup<Context>
6where
7 Context: XmtpSharedContext,
8{
9 #[xmtp_common::mls_span]
11 pub async fn send_message(
12 &self,
13 message: &[u8],
14 opts: send_message_opts::SendMessageOpts,
15 ) -> Result<Vec<u8>, GroupError> {
16 if !self.is_active()? {
17 tracing::warn!("Unable to send a message on an inactive group.");
18 return Err(GroupError::GroupInactive);
19 }
20
21 Self::reject_reserved_transcript_content(message)?;
23
24 self.ensure_not_paused().await?;
25 let update_interval_ns = Some(SEND_MESSAGE_UPDATE_INSTALLATIONS_INTERVAL_NS);
26 self.maybe_update_installations(update_interval_ns).await?;
27
28 self.commit_pending_proposals_if_any().await?;
31
32 let message_id = self.prepare_message(message, opts)?;
33
34 self.sync_until_last_intent_resolved().await?;
35
36 self.update_consent_state(ConsentState::Allowed)?;
38
39 Ok(message_id)
40 }
41
42 async fn commit_pending_proposals_if_any(&self) -> Result<(), GroupError> {
46 let (has_pending, has_pending_add) = self.with_group_snapshot(|openmls_group| {
47 Ok((
48 openmls_group.pending_proposals().next().is_some(),
49 openmls_group.pending_proposals().any(|proposal| {
50 matches!(proposal.proposal(), openmls::prelude::Proposal::Add(_))
51 }),
52 ))
53 })?;
54
55 if has_pending {
56 if has_pending_add {
60 self.add_missing_installations().await?;
61 }
62 tracing::debug!(
63 inbox_id = self.context.inbox_id(),
64 group_id = %self.group_id,
65 "Found pending proposals, committing before sending message"
66 );
67
68 let intent = intents::QueueIntent::commit_pending_proposals().queue(self)?;
70 self.sync_until_intent_resolved(intent.id).await?;
71 }
72
73 Ok(())
74 }
75
76 #[cfg_attr(any(test, feature = "test-utils"), tracing::instrument(level = "info", fields(inbox_id = self.context.inbox_id()), skip(self)))]
79 #[cfg_attr(not(any(test, feature = "test-utils")), xmtp_common::mls_span)]
80 pub async fn publish_messages(&self) -> Result<(), GroupError> {
81 self.ensure_not_paused().await?;
82 let update_interval_ns = Some(SEND_MESSAGE_UPDATE_INSTALLATIONS_INTERVAL_NS);
83 self.maybe_update_installations(update_interval_ns).await?;
84 self.sync_until_last_intent_resolved().await?;
85
86 self.update_consent_state(ConsentState::Allowed)?;
88
89 Ok(())
90 }
91
92 pub async fn update_installations(&self) -> Result<(), GroupError> {
97 self.ensure_not_paused().await?;
98 self.maybe_update_installations(Some(0)).await?;
99 Ok(())
100 }
101
102 pub fn send_message_optimistic(
104 &self,
105 message: &[u8],
106 opts: send_message_opts::SendMessageOpts,
107 ) -> Result<Vec<u8>, GroupError> {
108 let message_id = self.prepare_message(message, opts)?;
109 Ok(message_id)
110 }
111
112 pub fn prepare_message_for_later_publish(
125 &self,
126 message: &[u8],
127 should_push: bool,
128 idempotency_key: Option<String>,
129 ) -> Result<Vec<u8>, GroupError> {
130 state_write(self.context.mls_storage(), |tx| {
131 self.ensure_active_for_send(tx)?;
132 let storage = tx.storage();
133 let message = self.store_message_for_later_publish(
134 &storage.db(),
135 message,
136 should_push,
137 idempotency_key,
138 )?;
139 Ok::<_, GroupError>(Continue(message.id))
140 })
141 .map(TransactionOutcome::into_continued)
142 }
143
144 fn store_message_for_later_publish(
146 &self,
147 db: &impl DbQuery,
148 message: &[u8],
149 should_push: bool,
150 idempotency_key: Option<String>,
151 ) -> Result<StoredGroupMessage, GroupError> {
152 Self::reject_reserved_transcript_content(message)?;
153 let now = self.context.message_timestamp_ns();
154 let idempotency_key = idempotency_key.unwrap_or_else(|| {
156 hex::encode(xmtp_common::rand_vec::<DEFAULT_IDEMPOTENCY_KEY_BYTES>())
157 });
158 let queryable_content_fields = Self::extract_queryable_content_fields(message);
159
160 let message_id = calculate_message_id(self.group_id, message, &idempotency_key);
161
162 if let Some(existing) = db.get_group_message(&message_id)? {
166 return Ok(existing);
167 }
168
169 let group_message = StoredGroupMessage {
170 id: message_id.clone(),
171 group_id: self.group_id,
172 decrypted_message_bytes: message.to_vec(),
173 sent_at_ns: now,
174 kind: GroupMessageKind::Application,
175 sender_installation_id: self.context.installation_id().into(),
176 sender_inbox_id: self.context.inbox_id().to_string(),
177 delivery_status: DeliveryStatus::Unpublished,
178 content_type: queryable_content_fields.content_type,
179 version_major: queryable_content_fields.version_major,
180 version_minor: queryable_content_fields.version_minor,
181 authority_id: queryable_content_fields.authority_id,
182 reference_id: queryable_content_fields.reference_id,
183 sequence_id: 0,
184 envelope_hash: None,
185 expiry_ns: None,
186 expire_at_ns: None,
187 inserted_at_ns: 0,
188 should_push,
189 idempotency_key,
190 };
191 group_message.store(db)?;
192 Ok(group_message)
193 }
194
195 #[xmtp_common::mls_span]
203 pub async fn publish_stored_message(&self, message_id: &[u8]) -> Result<(), GroupError> {
204 if !self.is_active()? {
205 return Err(GroupError::GroupInactive);
206 }
207 self.ensure_not_paused().await?;
208
209 let queued = crate::state_tx::state_write_with_events(
210 self.context.mls_storage(),
211 self.context.events(),
212 |tx, events| {
213 self.ensure_active_for_send(tx)?;
214 let storage = tx.storage();
215 let db = storage.db();
216 let message = db
217 .get_group_message(message_id)?
218 .filter(|message| message.group_id == self.group_id)
219 .ok_or_else(|| {
220 GroupError::NotFound(NotFound::MessageById(message_id.to_vec()))
221 })?;
222 if message.delivery_status != DeliveryStatus::Published
228 && Self::is_reserved_transcript_content(&message.decrypted_message_bytes)
229 && !self.has_saved_attempt(&db, &message.id)?
230 {
231 if message.delivery_status == DeliveryStatus::Unpublished {
232 db.set_delivery_status_to_failed(&message.id)?;
233 self.emit_message_status_changed(
234 message.id,
235 xmtp_events::MessageStatus::Unpublished,
236 xmtp_events::MessageStatus::Failed,
237 events,
238 );
239 }
240 return Ok(Continue(Err(GroupError::ReservedTranscriptContentType)));
241 }
242 self.queue_stored_message(&db, events, &message)
243 .map(|queued| Continue(Ok(queued)))
244 },
245 )?
246 .into_continued()?;
247 if !queued {
248 return Ok(());
249 }
250
251 self.maybe_update_installations(Some(SEND_MESSAGE_UPDATE_INSTALLATIONS_INTERVAL_NS))
253 .await?;
254 self.sync_until_last_intent_resolved().await?;
255
256 self.update_consent_state(ConsentState::Allowed)?;
258
259 Ok(())
260 }
261
262 pub fn delete_message(&self, message_id: Vec<u8>) -> Result<Vec<u8>, GroupError> {
279 use error::DeleteMessageError;
280
281 let conn = self.context.db();
282
283 let original_msg = conn
285 .get_group_message(&message_id)?
286 .ok_or_else(|| DeleteMessageError::MessageNotFound(hex::encode(&message_id)))?;
287
288 if original_msg.group_id.as_slice() != self.group_id.as_slice() {
290 return Err(DeleteMessageError::NotAuthorized.into());
291 }
292
293 if conn.is_message_deleted(&message_id)? {
295 return Err(DeleteMessageError::MessageAlreadyDeleted.into());
296 }
297
298 let sender_inbox_id = self.context.inbox_id();
299 let is_sender = original_msg.sender_inbox_id == sender_inbox_id;
300 let is_super_admin = self.live_is_super_admin(sender_inbox_id)?;
301
302 if !is_sender && !is_super_admin {
303 return Err(DeleteMessageError::NotAuthorized.into());
304 }
305
306 if !crate::messages::enrichment::is_deletable_stored_message(&original_msg) {
307 return Err(DeleteMessageError::NonDeletableMessage.into());
308 }
309
310 let delete_msg = DeleteMessage {
311 message_id: hex::encode(&message_id),
312 };
313
314 let encoded_delete = DeleteMessageCodec::encode(delete_msg)?;
315 let mut buf = Vec::new();
316 encoded_delete.encode(&mut buf)?;
317
318 let deletion_message_id = self.send_message_optimistic(&buf, SendMessageOpts::default())?;
319
320 let is_super_admin_deletion = !is_sender && is_super_admin;
321
322 let deletion = StoredMessageDeletion {
323 id: deletion_message_id.clone(),
324 group_id: self.group_id,
325 deleted_message_id: message_id,
326 deleted_by_inbox_id: sender_inbox_id.to_string(),
327 is_super_admin_deletion,
328 deleted_at_ns: now_ns(),
329 };
330
331 crate::state_tx::state_write_with_events(
332 self.context.mls_storage(),
333 self.context.events(),
334 |tx, events| {
335 let storage = tx.storage();
336 let db = storage.db();
337 let already_deleted = db
338 .get_deletions_for_messages(vec![original_msg.id.clone()])?
339 .iter()
340 .any(|prior| {
341 crate::messages::enrichment::is_deletion_valid(
342 prior,
343 &original_msg,
344 &self.group_id,
345 )
346 });
347 deletion.store(&db)?;
348 if !already_deleted {
349 crate::subscriptions::internal::emit_deleted_messages(
350 events,
351 vec![original_msg],
352 xmtp_events::DeletionCause::DeletedLocally,
353 Some(deletion.deleted_by_inbox_id.clone()),
354 &db,
355 )?;
356 }
357 Ok::<_, GroupError>(Continue(()))
358 },
359 )?;
360
361 Ok(deletion_message_id)
362 }
363
364 fn has_saved_attempt(&self, db: &impl DbQuery, message_id: &[u8]) -> Result<bool, GroupError> {
366 for intent in db.find_group_intents(
367 self.group_id,
368 Some(vec![IntentState::Published]),
369 Some(vec![xmtp_db::group_intent::IntentKind::SendMessage]),
370 )? {
371 if crate::utils::id::calculate_message_id_for_intent(&intent)?.as_deref()
372 == Some(message_id)
373 && db.prepared_envelopes(intent.id)?.is_some()
374 {
375 return Ok(true);
376 }
377 }
378 Ok(false)
379 }
380
381 pub(in crate::groups) fn is_reserved_transcript_content(message: &[u8]) -> bool {
385 EncodedContent::decode(message)
386 .ok()
387 .and_then(|content| content.r#type)
388 .is_some_and(|content_type| {
389 content_type.authority_id == "xmtp.org"
390 && matches!(
391 content_type.type_id.as_str(),
392 "group_updated" | "group_membership_change"
393 )
394 })
395 }
396
397 fn reject_reserved_transcript_content(message: &[u8]) -> Result<(), GroupError> {
398 if Self::is_reserved_transcript_content(message) {
399 Err(GroupError::ReservedTranscriptContentType)
400 } else {
401 Ok(())
402 }
403 }
404
405 pub(in crate::groups) fn extract_queryable_content_fields(
407 message: &[u8],
408 ) -> QueryableContentFields {
409 EncodedContent::decode(message)
411 .inspect_err(|_| {
412 tracing::debug!("No queryable content fields, msg not formatted as encoded content")
413 })
414 .and_then(|content| {
415 QueryableContentFields::try_from(content).inspect_err(|e| {
416 tracing::debug!(
417 "Failed to convert EncodedContent to QueryableContentFields: {}",
418 e
419 )
420 })
421 })
422 .unwrap_or_default()
423 }
424
425 #[tracing::instrument(skip_all, level = "trace")]
432 pub(crate) fn prepare_message(
433 &self,
434 message: &[u8],
435 opts: send_message_opts::SendMessageOpts,
436 ) -> Result<Vec<u8>, GroupError> {
437 crate::state_tx::state_write_with_events(
438 self.context.mls_storage(),
439 self.context.events(),
440 |tx, events| {
441 self.ensure_active_for_send(tx)?;
442 let storage = tx.storage();
443 let db = storage.db();
444 let message = self.store_message_for_later_publish(
445 &db,
446 message,
447 opts.should_push,
448 opts.idempotency_key,
449 )?;
450 self.queue_stored_message(&db, events, &message)?;
451 Ok::<_, GroupError>(Continue(message.id))
452 },
453 )
454 .map(TransactionOutcome::into_continued)
455 }
456
457 fn queue_stored_message(
465 &self,
466 db: &impl DbQuery,
467 events: &impl xmtp_events::EventWriter<crate::subscriptions::internal::InternalEvent>,
468 message: &StoredGroupMessage,
469 ) -> Result<bool, GroupError> {
470 if message.delivery_status == DeliveryStatus::Published {
471 return Ok(false);
472 }
473 let envelope =
474 Self::into_envelope(&message.decrypted_message_bytes, &message.idempotency_key);
475 QueueIntent::send_message()
476 .data(Vec::<u8>::from(SendMessageIntentData::new(
477 envelope.encode_to_vec(),
478 )))
479 .should_push(message.should_push)
480 .queue_in(db, self)?;
481 if db.set_failed_delivery_status_to_unpublished(&message.id)? > 0 {
482 self.emit_message_status_changed(
483 message.id.clone(),
484 xmtp_events::MessageStatus::Failed,
485 xmtp_events::MessageStatus::Unpublished,
486 events,
487 );
488 }
489 Ok(true)
490 }
491
492 fn ensure_active_for_send<T: xmtp_db::TransactionalKeyStore>(
495 &self,
496 tx: &mut crate::state_tx::StateTx<'_, T>,
497 ) -> Result<(), GroupError> {
498 let stored = tx
499 .storage()
500 .db()
501 .find_group(&self.group_id)?
502 .ok_or(NotFound::GroupById(self.group_id))?;
503 if stored.membership_state == GroupMembershipState::Restored {
504 return Err(GroupError::GroupInactive);
505 }
506 let active = tx.with_group(self.group_id, |mls_group, _| {
507 Ok::<_, GroupError>(mls_group.is_active())
508 })?;
509 if active {
510 Ok(())
511 } else {
512 Err(GroupError::GroupInactive)
513 }
514 }
515
516 fn into_envelope(encoded_msg: &[u8], idempotency_key: &str) -> PlaintextEnvelope {
517 PlaintextEnvelope {
518 content: Some(Content::V1(V1 {
519 content: encoded_msg.to_vec(),
520 idempotency_key: idempotency_key.to_string(),
521 })),
522 }
523 }
524
525 pub fn find_messages(
528 &self,
529 args: &MsgQueryArgs,
530 ) -> Result<Vec<StoredGroupMessage>, GroupError> {
531 let conn = self.context.db();
532 let messages = conn.get_group_messages(&self.group_id, args)?;
533 Ok(messages)
534 }
535
536 pub fn count_messages(&self, args: &MsgQueryArgs) -> Result<i64, GroupError> {
538 let conn = self.context.db();
539 let count = conn.count_group_messages(&self.group_id, args)?;
540 Ok(count)
541 }
542
543 pub fn get_last_read_times(&self) -> Result<LatestMessageTimeBySender, GroupError> {
544 let conn = self.context.db();
545 let latest_read_receipt =
546 conn.get_latest_message_times_by_sender(self.group_id, &[ContentType::ReadReceipt])?;
547 Ok(latest_read_receipt)
548 }
549
550 pub fn load(&self) -> Result<StoredGroup, StorageError> {
552 let conn = self.context.db();
553 if let Some(group) = conn.find_group(&self.group_id)? {
554 Ok(group)
555 } else {
556 tracing::error!("group {} does not exist", hex::encode(self.group_id));
557 Err(NotFound::GroupById(self.group_id).into())
558 }
559 }
560}