1pub mod app_data;
2mod builders;
3mod lifecycle;
4mod membership;
5mod messages;
6mod metadata;
7mod metadata_fields;
8mod state;
9pub use builders::*;
10pub use state::{ConversationStateSnapshot, GroupMetadataSnapshot};
11pub mod change_callbacks;
12pub mod commit_log;
13pub mod commit_log_key;
14mod error;
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::GroupMutablePermissions,
36 group_permissions::PolicySet,
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};
49use crate::state_tx::state_write;
50use crate::{client::ClientError, utils::id::calculate_message_id};
51use crate::{
52 groups::send_message_opts::SendMessageOpts,
53 worker::device_sync::preference_sync::PreferenceUpdate,
54};
55pub use error::*;
56use intents::SendMessageIntentData;
57pub use intents::UpdateGroupMembershipResult;
58pub use mls_sync::GroupMessageProcessingError;
61#[cfg(test)]
62use openmls::extensions::Metadata;
63use openmls::{
64 credentials::CredentialType,
65 extensions::{
66 Extension, ExtensionType, Extensions, RequiredCapabilitiesExtension, UnknownExtension,
67 },
68 group::MlsGroupCreateConfig,
69 messages::proposals::ProposalType,
70 prelude::{Capabilities, MlsGroup as OpenMlsGroup, WireFormatPolicy},
71};
72use prost::Message;
73use std::collections::HashMap;
74use std::{collections::HashSet, sync::Arc};
75use tokio::sync::Mutex;
76use xmtp_common::{Event, log_event, time::now_ns};
77#[cfg(test)]
78use xmtp_configuration::GROUP_PERMISSIONS_EXTENSION_ID;
79use xmtp_configuration::{
80 CIPHERSUITE, GROUP_MEMBERSHIP_EXTENSION_ID, MAX_PAST_EPOCHS, MUTABLE_METADATA_EXTENSION_ID,
81 SEND_MESSAGE_UPDATE_INSTALLATIONS_INTERVAL_NS,
82 WELCOME_POINTEE_ENCRYPTION_AEAD_TYPES_EXTENSION_ID, WELCOME_WRAPPER_ENCRYPTION_EXTENSION_ID,
83};
84use xmtp_content_types::delete_message::DeleteMessageCodec;
85use xmtp_content_types::leave_request::LeaveRequestCodec;
86use xmtp_content_types::{ContentCodec, encoded_content_to_bytes};
87use xmtp_content_types::{
88 reaction::{LegacyReaction, ReactionCodec},
89 reply::ReplyCodec,
90};
91use xmtp_cryptography::configuration::ED25519_KEY_LENGTH;
92use xmtp_db::message_deletion::{QueryMessageDeletion, StoredMessageDeletion};
93use xmtp_db::pending_remove::QueryPendingRemove;
94use xmtp_db::prelude::*;
95use xmtp_db::user_preferences::HmacKey;
96use xmtp_db::{Fetch, consent_record::ConsentType};
97use xmtp_db::{
98 NotFound, StorageError,
99 group_intent::{IntentState, StoredGroupIntent},
100 group_message::ContentType,
101 refresh_state::EntityKind,
102};
103use xmtp_db::{Store, StoreOrIgnore};
104use xmtp_db::{TransactionOutcome, TransactionOutcome::Continue, XmtpOpenMlsProviderRef};
105use xmtp_db::{
106 XmtpMlsStorageProvider,
107 remote_commit_log::{RemoteCommitLog, RemoteCommitLogOrder},
108};
109use xmtp_db::{
110 consent_record::{ConsentState, StoredConsentRecord},
111 group::{ConversationType, GroupMembershipState, StoredGroup},
112 group_message::{DeliveryStatus, GroupMessageKind, MsgQueryArgs, StoredGroupMessage},
113};
114use xmtp_db::{group_message::LatestMessageTimeBySender, local_commit_log::LocalCommitLog};
115use xmtp_id::associations::Identifier;
116use xmtp_id::{AsIdRef, InboxId, InboxIdRef};
117use xmtp_mls_common::libxmtp_version::LibXMTPVersion;
118use xmtp_mls_common::{
119 app_data::components::{
120 inbox_id_set::{AdminListComponent, SuperAdminListComponent},
121 metadata_attributes::{
122 AppDataComponent, GroupDescriptionComponent, GroupImageUrlComponent, GroupNameComponent,
123 },
124 },
125 group::{DMMetadataOptions, GroupMetadataOptions},
126 group_metadata::{DmMembers, GroupMetadata, GroupMetadataError, extract_group_metadata},
127 group_mutable_metadata::{
128 GroupMutableMetadata, GroupMutableMetadataError, MessageDisappearingSettings, MetadataField,
129 },
130};
131pub use xmtp_mls_validation::{group_membership, group_permissions};
132use xmtp_proto::xmtp::mls::message_contents::content_types::{DeleteMessage, LeaveRequest};
133use xmtp_proto::{
134 types::{Cursor, GroupId},
135 xmtp::mls::message_contents::{
136 EncodedContent, OneshotMessage, PlaintextEnvelope,
137 content_types::ReactionV2,
138 plaintext_envelope::{Content, V1},
139 },
140};
141
142use xmtp_mls_common::app_data::components::metadata_attributes::{
143 MAX_APP_DATA_LENGTH, MAX_GROUP_DESCRIPTION_LENGTH, MAX_GROUP_IMAGE_URL_LENGTH,
144 MAX_GROUP_NAME_LENGTH,
145};
146const DEFAULT_IDEMPOTENCY_KEY_BYTES: usize = 16;
147
148pub struct MlsGroup<Context> {
153 pub group_id: GroupId,
154 pub dm_id: Option<String>,
155 pub conversation_type: ConversationType,
156 pub created_at_ns: i64,
157 pub context: Context,
158 #[cfg(test)]
159 mls_commit_lock: Arc<GroupCommitLock>,
160 mutex: Arc<Mutex<()>>,
161}
162
163impl<C> std::hash::Hash for MlsGroup<C> {
164 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
165 self.group_id.hash(state);
166 }
167}
168
169impl<C> PartialEq for MlsGroup<C> {
170 fn eq(&self, other: &Self) -> bool {
171 self.group_id == other.group_id
172 }
173}
174
175impl<C> Eq for MlsGroup<C> {}
176
177impl<Context> std::fmt::Debug for MlsGroup<Context>
178where
179 Context: XmtpSharedContext,
180{
181 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
182 let id = xmtp_common::fmt::truncate_hex(hex::encode(self.group_id));
183 let inbox_id = self.context.inbox_id();
184 let installation = self.context.installation_id().to_string();
185 let time = chrono::DateTime::from_timestamp_nanos(self.created_at_ns);
186 write!(
187 f,
188 "Group {{ id: [{}], created: [{}], client: [{}], installation: [{}] }}",
189 id,
190 time.format("%H:%M:%S"),
191 inbox_id,
192 installation
193 )
194 }
195}
196
197pub struct ConversationListItem<Context> {
198 pub group: MlsGroup<Context>,
199 pub membership_state: GroupMembershipState,
200 pub added_by_inbox_id: String,
201 pub last_message: Option<StoredGroupMessage>,
202 pub is_commit_log_forked: Option<bool>,
203}
204
205impl<Context: XmtpSharedContext> Clone for MlsGroup<Context> {
206 fn clone(&self) -> Self {
207 Self {
208 group_id: self.group_id,
209 dm_id: self.dm_id.clone(),
210 conversation_type: self.conversation_type,
211 created_at_ns: self.created_at_ns,
212 context: self.context.clone(),
213 mutex: self.mutex.clone(),
214 #[cfg(test)]
215 mls_commit_lock: self.mls_commit_lock.clone(),
216 }
217 }
218}
219
220#[derive(Debug, Clone, PartialEq)]
221pub struct ConversationDebugInfo {
222 pub epoch: u64,
223 pub maybe_forked: bool,
224 pub fork_details: String,
225 pub is_commit_log_forked: Option<bool>,
226 pub local_commit_log: String,
227 pub remote_commit_log: String,
228 pub cursor: Vec<Cursor>,
229}
230
231#[derive(Debug, Clone, PartialEq)]
232pub enum UpdateAdminListType {
233 Add,
234 Remove,
235 AddSuper,
236 RemoveSuper,
237}
238
239#[derive(Debug, Clone, Copy)]
240enum AdminListKind {
241 Admin,
242 SuperAdmin,
243}
244
245pub struct QueryableContentFields {
247 pub content_type: ContentType,
248 pub version_major: i32,
249 pub version_minor: i32,
250 pub authority_id: String,
251 pub reference_id: Option<Vec<u8>>,
252}
253
254impl Default for QueryableContentFields {
255 fn default() -> Self {
256 Self {
257 content_type: ContentType::Unknown, version_major: 0,
259 version_minor: 0,
260 authority_id: String::new(),
261 reference_id: None,
262 }
263 }
264}
265
266impl TryFrom<EncodedContent> for QueryableContentFields {
267 type Error = prost::DecodeError;
268
269 fn try_from(content: EncodedContent) -> Result<Self, Self::Error> {
270 let content_type_id = content.r#type.clone().unwrap_or_default();
271
272 let type_id_str = content_type_id.type_id.clone();
273
274 let decoded = xmtp_content_types::compression::decompress(content).ok();
277 let reference_id = decoded.and_then(|content| {
278 if content_type_id.authority_id != "xmtp.org" {
279 return None;
280 }
281 match (type_id_str.as_str(), content_type_id.version_major) {
282 (ReplyCodec::TYPE_ID, 1) => ReplyCodec::decode(content)
283 .ok()
284 .and_then(|reply| hex::decode(reply.reference).ok()),
285 (ReactionCodec::TYPE_ID, ReactionCodec::MAJOR_VERSION) => {
286 ReactionV2::decode(content.content.as_slice())
287 .ok()
288 .and_then(|reaction| hex::decode(reaction.reference).ok())
289 }
290 (ReactionCodec::TYPE_ID, 1) => LegacyReaction::decode(&content.content)
291 .and_then(|legacy_reaction| hex::decode(legacy_reaction.reference).ok()),
292 (DeleteMessageCodec::TYPE_ID, DeleteMessageCodec::MAJOR_VERSION) => {
293 DeleteMessage::decode(content.content.as_slice())
294 .ok()
295 .and_then(|delete_msg| hex::decode(delete_msg.message_id).ok())
296 }
297 _ => None,
298 }
299 });
300
301 Ok(QueryableContentFields {
302 content_type: ContentType::from_identifier(
303 &content_type_id.authority_id,
304 &content_type_id.type_id,
305 content_type_id.version_major,
306 ),
307 version_major: content_type_id.version_major as i32,
308 version_minor: content_type_id.version_minor as i32,
309 authority_id: content_type_id.authority_id.to_string(),
310 reference_id,
311 })
312 }
313}
314
315impl<Context: Clone> From<MlsGroup<&Context>> for MlsGroup<Context> {
316 fn from(group: MlsGroup<&Context>) -> MlsGroup<Context> {
317 MlsGroup::<Context> {
318 context: group.context.clone(),
319 group_id: group.group_id,
320 dm_id: group.dm_id,
321 created_at_ns: group.created_at_ns,
322 #[cfg(test)]
323 mls_commit_lock: group.mls_commit_lock,
324 mutex: group.mutex,
325 conversation_type: group.conversation_type,
326 }
327 }
328}
329
330#[derive(Debug, Clone, Copy, PartialEq, Eq)]
339pub enum MlsExtensionType {
340 ApplicationId,
341 RatchetTree,
342 RequiredCapabilities,
343 ExternalPub,
344 ExternalSenders,
345 LastResort,
346 ImmutableMetadata,
347 AppDataDictionary,
348 Unknown(u16),
350 Grease(u16),
352}
353
354impl From<ExtensionType> for MlsExtensionType {
355 fn from(value: ExtensionType) -> Self {
356 match value {
357 ExtensionType::ApplicationId => MlsExtensionType::ApplicationId,
358 ExtensionType::RatchetTree => MlsExtensionType::RatchetTree,
359 ExtensionType::RequiredCapabilities => MlsExtensionType::RequiredCapabilities,
360 ExtensionType::ExternalPub => MlsExtensionType::ExternalPub,
361 ExtensionType::ExternalSenders => MlsExtensionType::ExternalSenders,
362 ExtensionType::LastResort => MlsExtensionType::LastResort,
363 ExtensionType::ImmutableMetadata => MlsExtensionType::ImmutableMetadata,
364 ExtensionType::AppDataDictionary => MlsExtensionType::AppDataDictionary,
365 ExtensionType::Unknown(id) => MlsExtensionType::Unknown(id),
366 ExtensionType::Grease(id) => MlsExtensionType::Grease(id),
367 }
368 }
369}
370
371#[derive(Debug, Clone)]
373pub struct InstallationCapabilities {
374 pub installation_id: Vec<u8>,
375 pub is_own: bool,
377 pub supported_extensions: Vec<MlsExtensionType>,
381 pub capabilities_known: bool,
385}
386
387#[derive(Debug, Clone)]
390pub struct InboxCapabilities {
391 pub inbox_id: InboxId,
392 pub installations: Vec<InstallationCapabilities>,
393}
394
395#[derive(Debug, Clone)]
405pub struct GroupMembershipCapabilities {
406 pub context_extensions: Vec<MlsExtensionType>,
408 pub members: Vec<InboxCapabilities>,
411}