1pub mod app_data;
2pub mod hash {
3 pub use xmtp_cryptography::hash::sha256_bytes as sha256;
4}
5mod builders;
6mod lifecycle;
7mod membership;
8mod messages;
9mod metadata;
10mod metadata_fields;
11mod state;
12pub use crate::core::change_callbacks;
13pub use builders::*;
14pub use state::{ConversationStateSnapshot, GroupMetadataSnapshot};
15pub mod commit_log;
16pub mod context;
17pub(crate) mod deletion;
18#[cfg(any(test, feature = "diagnostics"))]
19pub(crate) mod diagnostics;
20mod error;
21pub mod intents;
22pub mod members;
23pub mod message_id;
24pub mod message_list;
25pub(super) mod mls_ext;
26pub(super) mod mls_sync;
27mod notifications;
28pub mod oneshot;
29pub mod send_message_opts;
30pub(super) mod subscriptions;
31pub mod summary;
32pub(crate) mod targets;
33#[cfg(any(test, feature = "test-utils"))]
34#[doc(hidden)]
35pub mod test_support;
36#[cfg(any(test, feature = "test-hooks"))]
37#[doc(hidden)]
38pub use mls_sync::test_mocks;
39#[cfg(test)]
40mod tests;
41mod traits;
42pub mod validated_commit;
43pub mod welcome_pointer;
44pub mod welcome_sync;
45mod welcomes;
46pub use welcomes::*;
47
48pub use self::group_permissions::PreconfiguredPolicies;
49use self::{
50 group_membership::GroupMembership,
51 group_permissions::GroupMutablePermissions,
52 group_permissions::PolicySet,
53 intents::{
54 AdminListActionType, PermissionPolicyOption, PermissionUpdateType,
55 UpdateAdminListIntentData, UpdateMetadataIntentData, UpdatePermissionIntentData,
56 },
57};
58use crate::context::XmtpSharedContext;
59#[cfg(any(test, feature = "test-hooks"))]
60use crate::core::commit_lock::GroupCommitLock;
61use crate::groups::message_id::{calculate_message_id, decode_message_reference};
62use crate::groups::send_message_opts::SendMessageOpts;
63use crate::groups::{
64 intents::{QueueIntent, ReaddInstallationsIntentData},
65 mls_ext::CommitLogStorer,
66};
67use crate::state_tx::state_write;
68pub use error::*;
69use intents::SendMessageIntentData;
70pub use intents::UpdateGroupMembershipResult;
71pub use mls_sync::GroupMessageProcessingError;
74use openmls::{
75 credentials::CredentialType,
76 extensions::{
77 Extension, ExtensionType, Extensions, RequiredCapabilitiesExtension, UnknownExtension,
78 },
79 group::MlsGroupCreateConfig,
80 messages::proposals::ProposalType,
81 prelude::{Capabilities, MlsGroup as OpenMlsGroup, WireFormatPolicy},
82};
83use prost::Message;
84use std::collections::HashMap;
85use std::{collections::HashSet, sync::Arc};
86use tokio::sync::Mutex;
87use xmtp_common::{Event, log_event, time::now_ns};
88use xmtp_configuration::{
89 GROUP_MEMBERSHIP_EXTENSION_ID, MAX_PAST_EPOCHS, SEND_MESSAGE_UPDATE_INSTALLATIONS_INTERVAL_NS,
90 WELCOME_POINTEE_ENCRYPTION_AEAD_TYPES_EXTENSION_ID, WELCOME_WRAPPER_ENCRYPTION_EXTENSION_ID,
91};
92use xmtp_content_types::delete_message::DeleteMessageCodec;
93use xmtp_content_types::leave_request::LeaveRequestCodec;
94use xmtp_content_types::{ContentCodec, encoded_content_to_bytes};
95use xmtp_content_types::{
96 reaction::{LegacyReaction, ReactionCodec},
97 reply::ReplyCodec,
98};
99use xmtp_cryptography::configuration::CIPHERSUITE;
100use xmtp_cryptography::secret::Zeroize;
101use xmtp_db::Fetch;
102use xmtp_db::pending_remove::QueryPendingRemove;
103use xmtp_db::prelude::*;
104use xmtp_db::user_preferences::HmacKey;
105use xmtp_db::{
106 NotFound, StorageError,
107 group_intent::{IntentState, StoredGroupIntent},
108 group_message::ContentType,
109 refresh_state::EntityKind,
110};
111use xmtp_db::{Store, StoreOrIgnore};
112use xmtp_db::{TransactionOutcome, TransactionOutcome::Continue, XmtpOpenMlsProviderRef};
113use xmtp_db::{
114 XmtpMlsStorageProvider,
115 remote_commit_log::{RemoteCommitLog, RemoteCommitLogOrder},
116};
117use xmtp_db::{
118 consent_record::{
119 ConsentActError, ConsentKey, ConsentState, ConsentWrite, Decision, Owed,
120 conversation_consent, write_consent,
121 },
122 group::{ConversationType, GroupMembershipState, StoredGroup},
123 group_message::{DeliveryStatus, GroupMessageKind, MsgQueryArgs, StoredGroupMessage},
124};
125use xmtp_db::{group_message::LatestMessageTimeBySender, local_commit_log::LocalCommitLog};
126use xmtp_id::associations::Identifier;
127use xmtp_id::{AsIdRef, InboxId, InboxIdRef};
128use xmtp_mls_group_state::libxmtp_version::LibXMTPVersion;
129use xmtp_mls_group_state::{
130 app_data::components::{
131 inbox_id_set::{AdminListComponent, SuperAdminListComponent},
132 metadata_attributes::{
133 AppDataComponent, GroupDescriptionComponent, GroupImageUrlComponent, GroupNameComponent,
134 },
135 },
136 group::GroupMetadataOptions,
137 group_metadata::{DmMembers, GroupMetadata, GroupMetadataError, extract_group_metadata},
138 group_mutable_metadata::{
139 GroupMutableMetadata, GroupMutableMetadataError, MessageDisappearingSettings, MetadataField,
140 },
141};
142pub use xmtp_mls_validation::{group_membership, group_permissions};
143use xmtp_proto::xmtp::mls::message_contents::content_types::{DeleteMessage, LeaveRequest};
144use xmtp_proto::{
145 types::{Cursor, GroupId},
146 xmtp::mls::message_contents::{
147 ContentTypeId, EncodedContent, OneshotMessage, PlaintextEnvelope,
148 content_types::ReactionV2,
149 plaintext_envelope::{Content, V1},
150 },
151};
152
153use xmtp_mls_group_state::app_data::components::metadata_attributes::{
154 MAX_APP_DATA_LENGTH, MAX_GROUP_DESCRIPTION_LENGTH, MAX_GROUP_IMAGE_URL_LENGTH,
155 MAX_GROUP_NAME_LENGTH,
156};
157const DEFAULT_IDEMPOTENCY_KEY_BYTES: usize = 16;
158
159pub struct MlsGroup<Context> {
164 pub group_id: GroupId,
165 pub dm_id: Option<String>,
166 pub conversation_type: ConversationType,
167 pub created_at_ns: i64,
168 pub context: Context,
169 #[cfg(any(test, feature = "test-hooks"))]
170 mls_commit_lock: Arc<GroupCommitLock>,
171 mutex: Arc<Mutex<()>>,
172}
173
174impl<C> std::hash::Hash for MlsGroup<C> {
175 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
176 self.group_id.hash(state);
177 }
178}
179
180impl<C> PartialEq for MlsGroup<C> {
181 fn eq(&self, other: &Self) -> bool {
182 self.group_id == other.group_id
183 }
184}
185
186impl<C> Eq for MlsGroup<C> {}
187
188impl<Context> std::fmt::Debug for MlsGroup<Context>
189where
190 Context: XmtpSharedContext,
191{
192 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
193 let id = xmtp_common::fmt::truncate_hex(hex::encode(self.group_id));
194 let inbox_id = self.context.inbox_id();
195 let installation = self.context.installation_id().to_string();
196 let time = chrono::DateTime::from_timestamp_nanos(self.created_at_ns);
197 write!(
198 f,
199 "Group {{ id: [{}], created: [{}], client: [{}], installation: [{}] }}",
200 id,
201 time.format("%H:%M:%S"),
202 inbox_id,
203 installation
204 )
205 }
206}
207
208pub struct ConversationListItem<Context> {
210 pub group: MlsGroup<Context>,
211 pub membership_state: GroupMembershipState,
212 pub added_by_inbox_id: String,
213 pub is_commit_log_forked: Option<bool>,
214 pub listing_time_ns: i64,
218}
219
220impl<Context: XmtpSharedContext> Clone for MlsGroup<Context> {
221 fn clone(&self) -> Self {
222 Self {
223 group_id: self.group_id,
224 dm_id: self.dm_id.clone(),
225 conversation_type: self.conversation_type,
226 created_at_ns: self.created_at_ns,
227 context: self.context.clone(),
228 mutex: self.mutex.clone(),
229 #[cfg(any(test, feature = "test-hooks"))]
230 mls_commit_lock: self.mls_commit_lock.clone(),
231 }
232 }
233}
234
235#[derive(Debug, Clone, PartialEq)]
236pub struct ConversationDebugInfo {
237 pub epoch: u64,
238 pub maybe_forked: bool,
239 pub fork_details: String,
240 pub is_commit_log_forked: Option<bool>,
241 pub local_commit_log: String,
242 pub remote_commit_log: String,
243 pub cursor: Vec<Cursor>,
244}
245
246#[derive(Debug, Clone, PartialEq)]
247pub enum UpdateAdminListType {
248 Add,
249 Remove,
250 AddSuper,
251 RemoveSuper,
252}
253
254#[derive(Debug, Clone, Copy)]
255enum AdminListKind {
256 Admin,
257 SuperAdmin,
258}
259
260pub struct QueryableContentFields {
262 pub content_type: ContentType,
263 pub version_major: i32,
264 pub version_minor: i32,
265 pub authority_id: String,
266 pub reference_id: Option<Vec<u8>>,
267}
268
269impl Default for QueryableContentFields {
270 fn default() -> Self {
271 Self {
272 content_type: ContentType::Unknown, version_major: 0,
274 version_minor: 0,
275 authority_id: String::new(),
276 reference_id: None,
277 }
278 }
279}
280
281impl QueryableContentFields {
282 pub(crate) fn has_reference(content_type_id: &ContentTypeId) -> bool {
284 content_type_id.authority_id == "xmtp.org"
285 && matches!(
286 (
287 content_type_id.type_id.as_str(),
288 content_type_id.version_major
289 ),
290 (ReplyCodec::TYPE_ID, 1)
291 | (ReactionCodec::TYPE_ID, ReactionCodec::MAJOR_VERSION)
292 | (ReactionCodec::TYPE_ID, 1)
293 | (
294 DeleteMessageCodec::TYPE_ID,
295 DeleteMessageCodec::MAJOR_VERSION
296 )
297 )
298 }
299
300 pub(crate) fn from_type(content_type_id: &ContentTypeId) -> Self {
302 QueryableContentFields {
303 content_type: ContentType::from_identifier(
304 &content_type_id.authority_id,
305 &content_type_id.type_id,
306 content_type_id.version_major,
307 ),
308 version_major: content_type_id.version_major as i32,
309 version_minor: content_type_id.version_minor as i32,
310 authority_id: content_type_id.authority_id.to_string(),
311 reference_id: None,
312 }
313 }
314}
315
316impl TryFrom<EncodedContent> for QueryableContentFields {
317 type Error = prost::DecodeError;
318
319 fn try_from(content: EncodedContent) -> Result<Self, Self::Error> {
320 let content_type_id = content.r#type.clone().unwrap_or_default();
321
322 let type_id_str = content_type_id.type_id.clone();
323
324 let mut decoded = xmtp_content_types::compression::decompress(content).ok();
329 let reference_id = decoded.as_ref().and_then(|content| {
330 if content_type_id.authority_id != "xmtp.org" {
331 return None;
332 }
333 match (type_id_str.as_str(), content_type_id.version_major) {
334 (ReplyCodec::TYPE_ID, 1) => ReplyCodec::decode(content.clone())
335 .ok()
336 .and_then(|reply| decode_message_reference(&reply.reference)),
337 (ReactionCodec::TYPE_ID, ReactionCodec::MAJOR_VERSION) => {
338 ReactionV2::decode(content.content.as_slice())
339 .ok()
340 .and_then(|reaction| decode_message_reference(&reaction.reference))
341 }
342 (ReactionCodec::TYPE_ID, 1) => {
343 LegacyReaction::decode(&content.content).and_then(|legacy_reaction| {
344 decode_message_reference(&legacy_reaction.reference)
345 })
346 }
347 (DeleteMessageCodec::TYPE_ID, DeleteMessageCodec::MAJOR_VERSION) => {
348 DeleteMessage::decode(content.content.as_slice())
349 .ok()
350 .and_then(|delete_msg| decode_message_reference(&delete_msg.message_id))
351 }
352 _ => None,
353 }
354 });
355 if let Some(content) = &mut decoded {
356 content.content.zeroize();
357 }
358
359 Ok(QueryableContentFields {
360 reference_id,
361 ..QueryableContentFields::from_type(&content_type_id)
362 })
363 }
364}
365
366impl<Context: Clone> From<MlsGroup<&Context>> for MlsGroup<Context> {
367 fn from(group: MlsGroup<&Context>) -> MlsGroup<Context> {
368 MlsGroup::<Context> {
369 context: group.context.clone(),
370 group_id: group.group_id,
371 dm_id: group.dm_id,
372 created_at_ns: group.created_at_ns,
373 #[cfg(any(test, feature = "test-hooks"))]
374 mls_commit_lock: group.mls_commit_lock,
375 mutex: group.mutex,
376 conversation_type: group.conversation_type,
377 }
378 }
379}
380
381#[derive(Debug, Clone, Copy, PartialEq, Eq)]
390pub enum MlsExtensionType {
391 ApplicationId,
392 RatchetTree,
393 RequiredCapabilities,
394 ExternalPub,
395 ExternalSenders,
396 LastResort,
397 AppDataDictionary,
398 Unknown(u16),
400 Grease(u16),
402}
403
404impl From<ExtensionType> for MlsExtensionType {
405 fn from(value: ExtensionType) -> Self {
406 match value {
407 ExtensionType::ApplicationId => MlsExtensionType::ApplicationId,
408 ExtensionType::RatchetTree => MlsExtensionType::RatchetTree,
409 ExtensionType::RequiredCapabilities => MlsExtensionType::RequiredCapabilities,
410 ExtensionType::ExternalPub => MlsExtensionType::ExternalPub,
411 ExtensionType::ExternalSenders => MlsExtensionType::ExternalSenders,
412 ExtensionType::LastResort => MlsExtensionType::LastResort,
413 ExtensionType::AppDataDictionary => MlsExtensionType::AppDataDictionary,
414 ExtensionType::Unknown(id) => MlsExtensionType::Unknown(id),
415 ExtensionType::Grease(id) => MlsExtensionType::Grease(id),
416 }
417 }
418}
419
420#[derive(Debug, Clone)]
422pub struct InstallationCapabilities {
423 pub installation_id: Vec<u8>,
424 pub is_own: bool,
426 pub supported_extensions: Vec<MlsExtensionType>,
430 pub capabilities_known: bool,
434}
435
436#[derive(Debug, Clone)]
439pub struct InboxCapabilities {
440 pub inbox_id: InboxId,
441 pub installations: Vec<InstallationCapabilities>,
442}
443
444#[derive(Debug, Clone)]
454pub struct GroupMembershipCapabilities {
455 pub context_extensions: Vec<MlsExtensionType>,
457 pub members: Vec<InboxCapabilities>,
460}