1pub mod app_data;
2mod builders;
3mod lifecycle;
4mod membership;
5mod messages;
6mod metadata;
7mod state;
8pub use builders::*;
9pub mod change_callbacks;
10pub mod commit_log;
11pub mod commit_log_key;
12mod error;
13pub mod group_membership;
14pub mod group_permissions;
15pub mod intents;
16pub mod members;
17pub mod message_list;
18pub(super) mod mls_ext;
19pub(super) mod mls_sync;
20pub mod oneshot;
21pub mod send_message_opts;
22pub(super) mod subscriptions;
23pub mod summary;
24#[cfg(test)]
25mod tests;
26pub mod validated_commit;
27pub mod welcome_pointer;
28pub mod welcome_sync;
29mod welcomes;
30pub use welcomes::*;
31
32pub use self::group_permissions::PreconfiguredPolicies;
33use self::{
34 group_membership::GroupMembership,
35 group_permissions::PolicySet,
36 group_permissions::{GroupMutablePermissions, extract_group_permissions},
37 intents::{
38 AdminListActionType, PermissionPolicyOption, PermissionUpdateType,
39 UpdateAdminListIntentData, UpdateMetadataIntentData, UpdatePermissionIntentData,
40 },
41};
42#[cfg(test)]
43use crate::GroupCommitLock;
44use crate::context::XmtpSharedContext;
45use crate::groups::{
46 intents::{QueueIntent, ReaddInstallationsIntentData},
47 mls_ext::CommitLogStorer,
48 validated_commit::LibXMTPVersion,
49};
50use crate::messages::enrichment::EnrichMessageError;
51use crate::state_tx::state_write;
52use crate::subscriptions::SyncWorkerEvent;
53use crate::{client::ClientError, subscriptions::LocalEvents, utils::id::calculate_message_id};
54use crate::{
55 groups::send_message_opts::SendMessageOpts,
56 worker::device_sync::preference_sync::PreferenceUpdate,
57};
58pub use error::*;
59use intents::SendMessageIntentData;
60pub use intents::UpdateGroupMembershipResult;
61use openmls::{
62 credentials::CredentialType,
63 extensions::{
64 Extension, ExtensionType, Extensions, Metadata, RequiredCapabilitiesExtension,
65 UnknownExtension,
66 },
67 group::{GroupContext, MlsGroupCreateConfig},
68 messages::proposals::ProposalType,
69 prelude::{Capabilities, MlsGroup as OpenMlsGroup, WireFormatPolicy},
70};
71use prost::Message;
72use std::collections::HashMap;
73use std::{collections::HashSet, sync::Arc};
74use tokio::sync::Mutex;
75use xmtp_common::{Event, log_event, time::now_ns};
76use xmtp_configuration::{
77 CIPHERSUITE, GROUP_MEMBERSHIP_EXTENSION_ID, GROUP_PERMISSIONS_EXTENSION_ID, MAX_PAST_EPOCHS,
78 MUTABLE_METADATA_EXTENSION_ID, SEND_MESSAGE_UPDATE_INSTALLATIONS_INTERVAL_NS,
79 WELCOME_POINTEE_ENCRYPTION_AEAD_TYPES_EXTENSION_ID, WELCOME_WRAPPER_ENCRYPTION_EXTENSION_ID,
80};
81use xmtp_content_types::delete_message::DeleteMessageCodec;
82use xmtp_content_types::leave_request::LeaveRequestCodec;
83use xmtp_content_types::{ContentCodec, encoded_content_to_bytes};
84use xmtp_content_types::{
85 reaction::{LegacyReaction, ReactionCodec},
86 reply::ReplyCodec,
87};
88use xmtp_cryptography::configuration::ED25519_KEY_LENGTH;
89use xmtp_db::group_message::Deletable;
90use xmtp_db::message_deletion::{QueryMessageDeletion, StoredMessageDeletion};
91use xmtp_db::pending_remove::QueryPendingRemove;
92use xmtp_db::prelude::*;
93use xmtp_db::user_preferences::HmacKey;
94use xmtp_db::{Fetch, consent_record::ConsentType};
95use xmtp_db::{
96 NotFound, StorageError,
97 group_intent::{IntentState, StoredGroupIntent},
98 group_message::{ContentType, StoredGroupMessageWithReactions},
99 refresh_state::EntityKind,
100};
101use xmtp_db::{Store, StoreOrIgnore};
102use xmtp_db::{TransactionOutcome, TransactionOutcome::Continue, XmtpOpenMlsProviderRef};
103use xmtp_db::{
104 XmtpMlsStorageProvider,
105 remote_commit_log::{RemoteCommitLog, RemoteCommitLogOrder},
106};
107use xmtp_db::{
108 consent_record::{ConsentState, StoredConsentRecord},
109 group::{ConversationType, GroupMembershipState, StoredGroup},
110 group_message::{DeliveryStatus, GroupMessageKind, MsgQueryArgs, StoredGroupMessage},
111};
112use xmtp_db::{group_message::LatestMessageTimeBySender, local_commit_log::LocalCommitLog};
113use xmtp_id::associations::Identifier;
114use xmtp_id::{AsIdRef, InboxId, InboxIdRef};
115use xmtp_mls_common::{
116 app_data::components::{
117 inbox_id_set::{AdminListComponent, SuperAdminListComponent},
118 metadata_attributes::{
119 AppDataComponent, GroupDescriptionComponent, GroupImageUrlComponent, GroupNameComponent,
120 },
121 },
122 group::{DMMetadataOptions, GroupMetadataOptions},
123 group_metadata::{DmMembers, GroupMetadata, GroupMetadataError, extract_group_metadata},
124 group_mutable_metadata::{
125 GroupMutableMetadata, GroupMutableMetadataError, MessageDisappearingSettings, MetadataField,
126 },
127};
128use xmtp_proto::xmtp::mls::message_contents::content_types::{DeleteMessage, LeaveRequest};
129use xmtp_proto::{
130 types::{Cursor, GroupId},
131 xmtp::mls::message_contents::{
132 EncodedContent, OneshotMessage, PlaintextEnvelope,
133 content_types::ReactionV2,
134 plaintext_envelope::{Content, V1},
135 },
136};
137
138const MAX_GROUP_DESCRIPTION_LENGTH: usize = 1000;
139const MAX_GROUP_NAME_LENGTH: usize = 100;
140const MAX_GROUP_IMAGE_URL_LENGTH: usize = 2048;
141const MAX_APP_DATA_LENGTH: usize = 8192;
142const DEFAULT_IDEMPOTENCY_KEY_BYTES: usize = 16;
143
144pub struct MlsGroup<Context> {
149 pub group_id: GroupId,
150 pub dm_id: Option<String>,
151 pub conversation_type: ConversationType,
152 pub created_at_ns: i64,
153 pub context: Context,
154 #[cfg(test)]
155 mls_commit_lock: Arc<GroupCommitLock>,
156 mutex: Arc<Mutex<()>>,
157}
158
159impl<C> std::hash::Hash for MlsGroup<C> {
160 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
161 self.group_id.hash(state);
162 }
163}
164
165impl<C> PartialEq for MlsGroup<C> {
166 fn eq(&self, other: &Self) -> bool {
167 self.group_id == other.group_id
168 }
169}
170
171impl<C> Eq for MlsGroup<C> {}
172
173impl<Context> std::fmt::Debug for MlsGroup<Context>
174where
175 Context: XmtpSharedContext,
176{
177 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
178 let id = xmtp_common::fmt::truncate_hex(hex::encode(self.group_id));
179 let inbox_id = self.context.inbox_id();
180 let installation = self.context.installation_id().to_string();
181 let time = chrono::DateTime::from_timestamp_nanos(self.created_at_ns);
182 write!(
183 f,
184 "Group {{ id: [{}], created: [{}], client: [{}], installation: [{}] }}",
185 id,
186 time.format("%H:%M:%S"),
187 inbox_id,
188 installation
189 )
190 }
191}
192
193pub struct ConversationListItem<Context> {
194 pub group: MlsGroup<Context>,
195 pub last_message: Option<StoredGroupMessage>,
196 pub is_commit_log_forked: Option<bool>,
197}
198
199impl<Context: XmtpSharedContext> Clone for MlsGroup<Context> {
200 fn clone(&self) -> Self {
201 Self {
202 group_id: self.group_id,
203 dm_id: self.dm_id.clone(),
204 conversation_type: self.conversation_type,
205 created_at_ns: self.created_at_ns,
206 context: self.context.clone(),
207 mutex: self.mutex.clone(),
208 #[cfg(test)]
209 mls_commit_lock: self.mls_commit_lock.clone(),
210 }
211 }
212}
213
214#[derive(Debug, Clone, PartialEq)]
215pub struct ConversationDebugInfo {
216 pub epoch: u64,
217 pub maybe_forked: bool,
218 pub fork_details: String,
219 pub is_commit_log_forked: Option<bool>,
220 pub local_commit_log: String,
221 pub remote_commit_log: String,
222 pub cursor: Vec<Cursor>,
223}
224
225#[derive(Debug, Clone, PartialEq)]
226pub enum UpdateAdminListType {
227 Add,
228 Remove,
229 AddSuper,
230 RemoveSuper,
231}
232
233#[derive(Debug, Clone, Default, PartialEq, Eq)]
241pub struct EnableProposalsOptions {
242 pub force: bool,
250
251 pub min_version: Option<String>,
270}
271
272impl EnableProposalsOptions {
273 #[cfg(any(test, feature = "test-utils"))]
282 pub fn test_default() -> Self {
283 Self {
284 force: false,
285 min_version: Some("0.0.0".to_string()),
286 }
287 }
288}
289
290#[derive(Debug, Clone, Copy)]
291enum AdminListKind {
292 Admin,
293 SuperAdmin,
294}
295
296pub struct QueryableContentFields {
298 pub content_type: ContentType,
299 pub version_major: i32,
300 pub version_minor: i32,
301 pub authority_id: String,
302 pub reference_id: Option<Vec<u8>>,
303}
304
305impl Default for QueryableContentFields {
306 fn default() -> Self {
307 Self {
308 content_type: ContentType::Unknown, version_major: 0,
310 version_minor: 0,
311 authority_id: String::new(),
312 reference_id: None,
313 }
314 }
315}
316
317impl TryFrom<EncodedContent> for QueryableContentFields {
318 type Error = prost::DecodeError;
319
320 fn try_from(content: EncodedContent) -> Result<Self, Self::Error> {
321 let content_type_id = content.r#type.clone().unwrap_or_default();
322
323 let type_id_str = content_type_id.type_id.clone();
324
325 let reference_id = match (type_id_str.as_str(), content_type_id.version_major) {
326 (ReplyCodec::TYPE_ID, 1) => ReplyCodec::decode(content)
327 .ok()
328 .and_then(|reply| hex::decode(reply.reference).ok()),
329 (ReactionCodec::TYPE_ID, major) if major >= 2 => {
330 ReactionV2::decode(content.content.as_slice())
331 .ok()
332 .and_then(|reaction| hex::decode(reaction.reference).ok())
333 }
334 (ReactionCodec::TYPE_ID, _) => LegacyReaction::decode(&content.content)
335 .and_then(|legacy_reaction| hex::decode(legacy_reaction.reference).ok()),
336 (DeleteMessageCodec::TYPE_ID, DeleteMessageCodec::MAJOR_VERSION) => {
337 DeleteMessage::decode(content.content.as_slice())
338 .ok()
339 .and_then(|delete_msg| hex::decode(delete_msg.message_id).ok())
340 }
341 _ => None,
342 };
343
344 Ok(QueryableContentFields {
345 content_type: content_type_id.type_id.into(),
346 version_major: content_type_id.version_major as i32,
347 version_minor: content_type_id.version_minor as i32,
348 authority_id: content_type_id.authority_id.to_string(),
349 reference_id,
350 })
351 }
352}
353
354impl<Context: Clone> From<MlsGroup<&Context>> for MlsGroup<Context> {
355 fn from(group: MlsGroup<&Context>) -> MlsGroup<Context> {
356 MlsGroup::<Context> {
357 context: group.context.clone(),
358 group_id: group.group_id,
359 dm_id: group.dm_id,
360 created_at_ns: group.created_at_ns,
361 #[cfg(test)]
362 mls_commit_lock: group.mls_commit_lock,
363 mutex: group.mutex,
364 conversation_type: group.conversation_type,
365 }
366 }
367}
368
369#[derive(Debug, Clone, Copy, PartialEq, Eq)]
378pub enum MlsExtensionType {
379 ApplicationId,
380 RatchetTree,
381 RequiredCapabilities,
382 ExternalPub,
383 ExternalSenders,
384 LastResort,
385 ImmutableMetadata,
386 AppDataDictionary,
387 Unknown(u16),
389 Grease(u16),
391}
392
393impl From<ExtensionType> for MlsExtensionType {
394 fn from(value: ExtensionType) -> Self {
395 match value {
396 ExtensionType::ApplicationId => MlsExtensionType::ApplicationId,
397 ExtensionType::RatchetTree => MlsExtensionType::RatchetTree,
398 ExtensionType::RequiredCapabilities => MlsExtensionType::RequiredCapabilities,
399 ExtensionType::ExternalPub => MlsExtensionType::ExternalPub,
400 ExtensionType::ExternalSenders => MlsExtensionType::ExternalSenders,
401 ExtensionType::LastResort => MlsExtensionType::LastResort,
402 ExtensionType::ImmutableMetadata => MlsExtensionType::ImmutableMetadata,
403 ExtensionType::AppDataDictionary => MlsExtensionType::AppDataDictionary,
404 ExtensionType::Unknown(id) => MlsExtensionType::Unknown(id),
405 ExtensionType::Grease(id) => MlsExtensionType::Grease(id),
406 }
407 }
408}
409
410#[derive(Debug, Clone)]
412pub struct InstallationCapabilities {
413 pub installation_id: Vec<u8>,
414 pub is_own: bool,
416 pub supported_extensions: Vec<MlsExtensionType>,
420 pub capabilities_known: bool,
424}
425
426#[derive(Debug, Clone)]
429pub struct InboxCapabilities {
430 pub inbox_id: InboxId,
431 pub installations: Vec<InstallationCapabilities>,
432}
433
434#[derive(Debug, Clone)]
444pub struct GroupMembershipCapabilities {
445 pub context_extensions: Vec<MlsExtensionType>,
447 pub members: Vec<InboxCapabilities>,
450}