1use xmtp_configuration::{
13 ApplicationComponentDefinition, AttachmentsConfiguration, AuthConfiguration,
14 BACKEND_DEFAULT_MAX_UPLOAD_BYTES, ComponentPermissions, HandlesConfiguration,
15 LimitsConfiguration, MAX_IDENTITY_UPDATE_ACTIONS, MetadataPolicy, MlsConfiguration,
16 RetentionConfiguration, ServerConfiguration, SigningKeyDescription,
17 attachments::{
18 AttachmentConfigurationError, check_base_url, check_max_upload_bytes,
19 check_retention_seconds,
20 },
21};
22
23use crate::backend_v1;
24use crate::xmtp::mls::message_contents::{
25 self as mls,
26 metadata_policy::{AndCondition, AnyCondition, Kind},
27};
28
29fn or_default<T, W>(published: W, default: T) -> T
32where
33 T: Copy + PartialEq + Default + TryFrom<W>,
34 W: Copy,
35{
36 T::try_from(published)
37 .ok()
38 .filter(|value| *value != T::default())
39 .unwrap_or(default)
40}
41
42impl From<backend_v1::AuthConfiguration> for AuthConfiguration {
43 fn from(auth: backend_v1::AuthConfiguration) -> Self {
44 Self {
45 enabled: auth.enabled,
46 keys: auth
47 .keys
48 .into_iter()
49 .map(|key| SigningKeyDescription {
50 kid: key.kid,
51 alg: key.alg,
52 })
53 .collect(),
54 audiences: auth.audiences,
55 issuers: auth.issuers,
56 required_scopes: auth.required_scopes,
57 }
58 }
59}
60
61impl From<backend_v1::RetentionConfiguration> for RetentionConfiguration {
62 fn from(retention: backend_v1::RetentionConfiguration) -> Self {
63 let default = Self::default();
64 Self {
65 group_message_seconds: or_default(
66 retention.group_message_seconds,
67 default.group_message_seconds,
68 ),
69 welcome_seconds: or_default(retention.welcome_seconds, default.welcome_seconds),
70 key_package_seconds: or_default(
71 retention.key_package_seconds,
72 default.key_package_seconds,
73 ),
74 }
75 }
76}
77
78impl From<backend_v1::LimitsConfiguration> for LimitsConfiguration {
79 fn from(limits: backend_v1::LimitsConfiguration) -> Self {
80 let default = Self::default();
81 Self {
82 max_envelope_bytes: or_default(limits.max_envelope_bytes, default.max_envelope_bytes),
83 max_request_bytes: or_default(limits.max_request_bytes, default.max_request_bytes),
84 max_response_bytes: or_default(limits.max_response_bytes, default.max_response_bytes),
85 max_publish_topics: or_default(limits.max_publish_topics, default.max_publish_topics),
86 max_query_topics: or_default(limits.max_query_topics, default.max_query_topics),
87 max_query_limit: or_default(limits.max_query_limit, default.max_query_limit),
88 default_query_limit: or_default(
89 limits.default_query_limit,
90 default.default_query_limit,
91 ),
92 max_newest_metadata_topics: or_default(
93 limits.max_newest_metadata_topics,
94 default.max_newest_metadata_topics,
95 ),
96 max_newest_full_topics: or_default(
97 limits.max_newest_full_topics,
98 default.max_newest_full_topics,
99 ),
100 max_update_adds: or_default(limits.max_update_adds, default.max_update_adds),
101 max_update_removes: or_default(limits.max_update_removes, default.max_update_removes),
102 max_stream_topics: or_default(limits.max_stream_topics, default.max_stream_topics),
103 max_static_topics: or_default(limits.max_static_topics, default.max_static_topics),
104 max_lookup_identifiers: or_default(
105 limits.max_lookup_identifiers,
106 default.max_lookup_identifiers,
107 ),
108 max_scw_signatures: or_default(limits.max_scw_signatures, default.max_scw_signatures),
109 max_identity_entries: or_default(
110 limits.max_identity_entries,
111 default.max_identity_entries,
112 ),
113 max_update_frames_per_second: or_default(
114 limits.max_update_frames_per_second,
115 default.max_update_frames_per_second,
116 ),
117 max_update_burst: or_default(limits.max_update_burst, default.max_update_burst),
118 max_ping_frames_per_second: or_default(
119 limits.max_ping_frames_per_second,
120 default.max_ping_frames_per_second,
121 ),
122 max_ping_burst: or_default(limits.max_ping_burst, default.max_ping_burst),
123 }
124 }
125}
126
127impl From<backend_v1::MlsConfiguration> for MlsConfiguration {
128 fn from(mls: backend_v1::MlsConfiguration) -> Self {
129 let default = Self::default();
130 Self {
131 max_group_members: or_default(mls.max_group_members, default.max_group_members),
132 max_installations_per_inbox: or_default(
135 mls.max_installations_per_inbox,
136 default.max_installations_per_inbox,
137 )
138 .min(MAX_IDENTITY_UPDATE_ACTIONS),
139 commit_log_enabled: mls.commit_log_enabled,
142 }
143 }
144}
145
146impl From<backend_v1::HandlesConfiguration> for HandlesConfiguration {
147 fn from(handles: backend_v1::HandlesConfiguration) -> Self {
148 Self {
149 min_name_length: or_default(handles.min_name_length, Self::default().min_name_length),
150 }
151 }
152}
153
154impl From<mls::MetadataPolicy> for MetadataPolicy {
155 fn from(policy: mls::MetadataPolicy) -> Self {
156 let all =
157 |policies: Vec<mls::MetadataPolicy>| policies.into_iter().map(Self::from).collect();
158 match policy.kind {
159 Some(Kind::Base(base)) => Self::Base(base),
160 Some(Kind::AndCondition(and)) => Self::And(all(and.policies)),
161 Some(Kind::AnyCondition(any)) => Self::Any(all(any.policies)),
162 None => Self::Base(0),
164 }
165 }
166}
167
168impl From<MetadataPolicy> for mls::MetadataPolicy {
169 fn from(policy: MetadataPolicy) -> Self {
170 let all = |policies: Vec<MetadataPolicy>| policies.into_iter().map(Self::from).collect();
171 let kind = match policy {
172 MetadataPolicy::Base(base) => Kind::Base(base),
173 MetadataPolicy::And(policies) => Kind::AndCondition(AndCondition {
174 policies: all(policies),
175 }),
176 MetadataPolicy::Any(policies) => Kind::AnyCondition(AnyCondition {
177 policies: all(policies),
178 }),
179 };
180 Self { kind: Some(kind) }
181 }
182}
183
184impl From<mls::ComponentPermissions> for ComponentPermissions {
185 fn from(permissions: mls::ComponentPermissions) -> Self {
186 Self {
187 insert: permissions.insert_policy.map(Into::into),
188 update: permissions.update_policy.map(Into::into),
189 delete: permissions.delete_policy.map(Into::into),
190 }
191 }
192}
193
194impl From<ComponentPermissions> for mls::ComponentPermissions {
195 fn from(permissions: ComponentPermissions) -> Self {
196 Self {
197 insert_policy: permissions.insert.map(Into::into),
198 update_policy: permissions.update.map(Into::into),
199 delete_policy: permissions.delete.map(Into::into),
200 }
201 }
202}
203
204impl From<backend_v1::ApplicationComponentDefinition> for ApplicationComponentDefinition {
205 fn from(definition: backend_v1::ApplicationComponentDefinition) -> Self {
206 Self {
207 component_id: u16::try_from(definition.component_id).unwrap_or(u16::MAX),
210 name: definition.name,
211 component_type: definition.component_type,
212 permissions: definition.permissions.unwrap_or_default().into(),
213 in_groups: definition.in_groups,
214 in_dms: definition.in_dms,
215 }
216 }
217}
218
219impl From<ApplicationComponentDefinition> for backend_v1::ApplicationComponentDefinition {
220 fn from(definition: ApplicationComponentDefinition) -> Self {
221 Self {
222 component_id: definition.component_id.into(),
223 name: definition.name,
224 component_type: definition.component_type,
225 permissions: Some(definition.permissions.into()),
226 in_groups: definition.in_groups,
227 in_dms: definition.in_dms,
228 }
229 }
230}
231
232impl TryFrom<backend_v1::AttachmentsConfiguration> for AttachmentsConfiguration {
234 type Error = AttachmentConfigurationError;
235
236 fn try_from(attachments: backend_v1::AttachmentsConfiguration) -> Result<Self, Self::Error> {
237 let max_upload_bytes = or_default(
238 attachments.max_upload_bytes,
239 BACKEND_DEFAULT_MAX_UPLOAD_BYTES,
240 );
241 check_base_url(&attachments.base_url)?;
242 check_max_upload_bytes(max_upload_bytes)?;
243 check_retention_seconds(attachments.retention_seconds)?;
244 Ok(Self {
245 base_url: attachments.base_url,
246 max_upload_bytes,
247 retention_seconds: attachments.retention_seconds,
248 })
249 }
250}
251
252impl From<backend_v1::GetConfigurationResponse> for ServerConfiguration {
253 fn from(response: backend_v1::GetConfigurationResponse) -> Self {
254 let attachments = response.attachments.and_then(|message| {
255 match AttachmentsConfiguration::try_from(message) {
256 Ok(attachments) => Some(attachments),
257 Err(reason) => {
258 tracing::warn!(
259 field = reason.field(),
260 reason = reason.reason(),
261 "ignoring unusable attachment storage offer"
262 );
263 None
264 }
265 }
266 });
267 Self {
268 identifier: response.identifier,
269 server_version: response.server_version,
270 min_libxmtp_version: response.min_libxmtp_version,
271 auth: response.auth.unwrap_or_default().into(),
272 retention: response.retention.unwrap_or_default().into(),
273 limits: response.limits.unwrap_or_default().into(),
274 mls: response.mls.unwrap_or_default().into(),
275 handles: response.handles.unwrap_or_default().into(),
276 attachments,
277 smart_contract_wallet_chains: response.smart_contract_wallet_chains,
278 application_components: response
279 .application_components
280 .into_iter()
281 .map(Into::into)
282 .collect(),
283 }
284 }
285}
286
287#[cfg(test)]
288mod tests;