1use xmtp_configuration::{
13 ApplicationComponentDefinition, AttachmentsConfiguration, AuthConfiguration,
14 BACKEND_DEFAULT_MAX_UPLOAD_BYTES, ComponentPermissions, HandlesConfiguration,
15 LimitsConfiguration, MetadataPolicy, MlsConfiguration, RetentionConfiguration,
16 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(
133 mls.max_installations_per_inbox,
134 default.max_installations_per_inbox,
135 ),
136 commit_log_enabled: mls.commit_log_enabled,
139 }
140 }
141}
142
143impl From<backend_v1::HandlesConfiguration> for HandlesConfiguration {
144 fn from(handles: backend_v1::HandlesConfiguration) -> Self {
145 Self {
146 min_name_length: or_default(handles.min_name_length, Self::default().min_name_length),
147 }
148 }
149}
150
151impl From<mls::MetadataPolicy> for MetadataPolicy {
152 fn from(policy: mls::MetadataPolicy) -> Self {
153 let all =
154 |policies: Vec<mls::MetadataPolicy>| policies.into_iter().map(Self::from).collect();
155 match policy.kind {
156 Some(Kind::Base(base)) => Self::Base(base),
157 Some(Kind::AndCondition(and)) => Self::And(all(and.policies)),
158 Some(Kind::AnyCondition(any)) => Self::Any(all(any.policies)),
159 None => Self::Base(0),
161 }
162 }
163}
164
165impl From<MetadataPolicy> for mls::MetadataPolicy {
166 fn from(policy: MetadataPolicy) -> Self {
167 let all = |policies: Vec<MetadataPolicy>| policies.into_iter().map(Self::from).collect();
168 let kind = match policy {
169 MetadataPolicy::Base(base) => Kind::Base(base),
170 MetadataPolicy::And(policies) => Kind::AndCondition(AndCondition {
171 policies: all(policies),
172 }),
173 MetadataPolicy::Any(policies) => Kind::AnyCondition(AnyCondition {
174 policies: all(policies),
175 }),
176 };
177 Self { kind: Some(kind) }
178 }
179}
180
181impl From<mls::ComponentPermissions> for ComponentPermissions {
182 fn from(permissions: mls::ComponentPermissions) -> Self {
183 Self {
184 insert: permissions.insert_policy.map(Into::into),
185 update: permissions.update_policy.map(Into::into),
186 delete: permissions.delete_policy.map(Into::into),
187 }
188 }
189}
190
191impl From<ComponentPermissions> for mls::ComponentPermissions {
192 fn from(permissions: ComponentPermissions) -> Self {
193 Self {
194 insert_policy: permissions.insert.map(Into::into),
195 update_policy: permissions.update.map(Into::into),
196 delete_policy: permissions.delete.map(Into::into),
197 }
198 }
199}
200
201impl From<backend_v1::ApplicationComponentDefinition> for ApplicationComponentDefinition {
202 fn from(definition: backend_v1::ApplicationComponentDefinition) -> Self {
203 Self {
204 component_id: u16::try_from(definition.component_id).unwrap_or(u16::MAX),
207 name: definition.name,
208 component_type: definition.component_type,
209 permissions: definition.permissions.unwrap_or_default().into(),
210 in_groups: definition.in_groups,
211 in_dms: definition.in_dms,
212 }
213 }
214}
215
216impl From<ApplicationComponentDefinition> for backend_v1::ApplicationComponentDefinition {
217 fn from(definition: ApplicationComponentDefinition) -> Self {
218 Self {
219 component_id: definition.component_id.into(),
220 name: definition.name,
221 component_type: definition.component_type,
222 permissions: Some(definition.permissions.into()),
223 in_groups: definition.in_groups,
224 in_dms: definition.in_dms,
225 }
226 }
227}
228
229impl TryFrom<backend_v1::AttachmentsConfiguration> for AttachmentsConfiguration {
231 type Error = AttachmentConfigurationError;
232
233 fn try_from(attachments: backend_v1::AttachmentsConfiguration) -> Result<Self, Self::Error> {
234 let max_upload_bytes = or_default(
235 attachments.max_upload_bytes,
236 BACKEND_DEFAULT_MAX_UPLOAD_BYTES,
237 );
238 check_base_url(&attachments.base_url)?;
239 check_max_upload_bytes(max_upload_bytes)?;
240 check_retention_seconds(attachments.retention_seconds)?;
241 Ok(Self {
242 base_url: attachments.base_url,
243 max_upload_bytes,
244 retention_seconds: attachments.retention_seconds,
245 })
246 }
247}
248
249impl From<backend_v1::GetConfigurationResponse> for ServerConfiguration {
250 fn from(response: backend_v1::GetConfigurationResponse) -> Self {
251 let attachments = response.attachments.and_then(|message| {
252 match AttachmentsConfiguration::try_from(message) {
253 Ok(attachments) => Some(attachments),
254 Err(reason) => {
255 tracing::warn!(
256 field = reason.field(),
257 reason = reason.reason(),
258 "ignoring unusable attachment storage offer"
259 );
260 None
261 }
262 }
263 });
264 Self {
265 identifier: response.identifier,
266 server_version: response.server_version,
267 min_libxmtp_version: response.min_libxmtp_version,
268 auth: response.auth.unwrap_or_default().into(),
269 retention: response.retention.unwrap_or_default().into(),
270 limits: response.limits.unwrap_or_default().into(),
271 mls: response.mls.unwrap_or_default().into(),
272 handles: response.handles.unwrap_or_default().into(),
273 attachments,
274 smart_contract_wallet_chains: response.smart_contract_wallet_chains,
275 application_components: response
276 .application_components
277 .into_iter()
278 .map(Into::into)
279 .collect(),
280 }
281 }
282}
283
284#[cfg(test)]
285mod tests;