Skip to main content

xmtp_proto/
lib.rs

1#[allow(clippy::all)]
2#[allow(warnings)]
3mod generated {
4    //! Module structure of Protos for XMTP
5
6    include!(concat!(env!("OUT_DIR"), "/mod.rs"));
7    pub const FILE_DESCRIPTOR_SET: &'static [u8] =
8        include_bytes!(concat!(env!("OUT_DIR"), "/proto_descriptor.bin"));
9}
10
11pub mod api_client;
12pub mod codec;
13mod convert;
14mod error;
15mod impls;
16mod secret_drop;
17mod traits;
18pub mod types;
19
20pub use error::*;
21pub use generated::*;
22pub use impls::update_dedupe::GroupUpdateDeduper;
23pub use traits::short_hex::ShortHex;
24
25pub mod api {
26    pub use super::traits::combinators::*;
27    pub use super::traits::stream::*;
28    pub use super::traits::*;
29}
30
31#[cfg(test)]
32pub mod test {
33    xmtp_common::if_native! {
34        #[cfg(test)]
35        #[ctor::ctor(unsafe)]
36        fn _setup() {
37            xmtp_common::logger()
38        }
39    }
40}
41
42pub mod prelude {
43    pub use super::FILE_DESCRIPTOR_SET;
44    xmtp_common::if_test! {
45        pub use super::api_client::XmtpTestClient;
46    }
47    pub use super::api_client::{
48        ApiBuilder, ArcedXmtpApi, BoxedXmtpApi, NetConnectConfig, XmtpBackendClient, XmtpMlsStreams,
49    };
50    pub use super::traits::{ApiClientError, BytesStream, Client, Endpoint, Query, QueryStream};
51}
52
53pub mod identity_v1 {
54    pub use super::xmtp::identity::api::v1::*;
55}
56
57pub mod backend_v1 {
58    pub use super::xmtp::backend::v1::*;
59}
60
61#[cfg(test)]
62mod descriptor_tests {
63    use prost::Message;
64    use prost_types::field_descriptor_proto::{Label, Type};
65
66    type ExpectedField = (&'static str, i32, Type, Label, Option<&'static str>);
67
68    fn assert_fields(message: &prost_types::DescriptorProto, expected: &[ExpectedField]) {
69        let message_name = message.name.as_deref().expect("message name");
70        let mut numbers = std::collections::HashSet::new();
71        for field in &message.field {
72            assert!(
73                numbers.insert(field.number),
74                "{message_name} reuses a field number"
75            );
76        }
77        for &(name, number, kind, label, type_name) in expected {
78            let field = message
79                .field
80                .iter()
81                .find(|field| field.name.as_deref() == Some(name))
82                .unwrap_or_else(|| panic!("{message_name}.{name} descriptor"));
83            assert_eq!(field.number, Some(number), "{message_name}.{name}");
84            assert_eq!(field.r#type(), kind, "{message_name}.{name}");
85            assert_eq!(field.label(), label, "{message_name}.{name}");
86            assert_eq!(
87                field.type_name.as_deref(),
88                type_name,
89                "{message_name}.{name}"
90            );
91        }
92    }
93
94    // verifies: CONF-017
95    #[xmtp_common::test(unwrap_try = true)]
96    fn public_configuration_fields_match_the_wire_contract() {
97        use Label::{Optional, Repeated};
98        use Type::{Bool, Message, String, Uint32, Uint64};
99
100        let descriptors = prost_types::FileDescriptorSet::decode(crate::FILE_DESCRIPTOR_SET)?;
101        let file = descriptors
102            .file
103            .iter()
104            .find(|file| {
105                file.package.as_deref() == Some("xmtp.backend.v1")
106                    && file
107                        .message_type
108                        .iter()
109                        .any(|message| message.name.as_deref() == Some("GetConfigurationResponse"))
110            })
111            .expect("backend configuration file descriptor");
112        let message = |name: &str| {
113            file.message_type
114                .iter()
115                .find(|message| message.name.as_deref() == Some(name))
116                .unwrap_or_else(|| panic!("{name} descriptor"))
117        };
118
119        assert!(message("GetConfigurationRequest").field.is_empty());
120        let auth = message("AuthConfiguration");
121        let signing_key = auth
122            .nested_type
123            .iter()
124            .find(|message| message.name.as_deref() == Some("SigningKey"))
125            .expect("AuthConfiguration.SigningKey descriptor");
126        assert_fields(
127            signing_key,
128            &[
129                ("kid", 1, String, Optional, None),
130                ("alg", 2, String, Optional, None),
131            ],
132        );
133        assert_fields(
134            auth,
135            &[
136                ("enabled", 1, Bool, Optional, None),
137                (
138                    "keys",
139                    2,
140                    Message,
141                    Repeated,
142                    Some(".xmtp.backend.v1.AuthConfiguration.SigningKey"),
143                ),
144                ("audiences", 3, String, Repeated, None),
145                ("issuers", 4, String, Repeated, None),
146                ("required_scopes", 5, String, Repeated, None),
147            ],
148        );
149        assert_fields(
150            message("RetentionConfiguration"),
151            &[
152                ("group_message_seconds", 1, Uint64, Optional, None),
153                ("welcome_seconds", 2, Uint64, Optional, None),
154                ("key_package_seconds", 3, Uint64, Optional, None),
155            ],
156        );
157        assert_fields(
158            message("LimitsConfiguration"),
159            &[
160                ("max_envelope_bytes", 1, Uint64, Optional, None),
161                ("max_request_bytes", 2, Uint64, Optional, None),
162                ("max_response_bytes", 3, Uint64, Optional, None),
163                ("max_publish_topics", 4, Uint32, Optional, None),
164                ("max_query_topics", 5, Uint32, Optional, None),
165                ("max_query_limit", 6, Uint32, Optional, None),
166                ("default_query_limit", 7, Uint32, Optional, None),
167                ("max_newest_metadata_topics", 8, Uint32, Optional, None),
168                ("max_newest_full_topics", 9, Uint32, Optional, None),
169                ("max_update_adds", 10, Uint32, Optional, None),
170                ("max_update_removes", 11, Uint32, Optional, None),
171                ("max_stream_topics", 12, Uint32, Optional, None),
172                ("max_static_topics", 13, Uint32, Optional, None),
173                ("max_lookup_identifiers", 14, Uint32, Optional, None),
174                ("max_scw_signatures", 15, Uint32, Optional, None),
175                ("max_identity_entries", 16, Uint32, Optional, None),
176                ("max_update_frames_per_second", 17, Uint32, Optional, None),
177                ("max_update_burst", 18, Uint32, Optional, None),
178                ("max_ping_frames_per_second", 19, Uint32, Optional, None),
179                ("max_ping_burst", 20, Uint32, Optional, None),
180            ],
181        );
182        let mls = message("MlsConfiguration");
183        assert_fields(
184            mls,
185            &[
186                ("max_group_members", 1, Uint32, Optional, None),
187                ("max_installations_per_inbox", 2, Uint32, Optional, None),
188                ("commit_log_enabled", 3, Bool, Optional, None),
189            ],
190        );
191        let commit_log = mls
192            .field
193            .iter()
194            .find(|field| field.name.as_deref() == Some("commit_log_enabled"))
195            .expect("commit_log_enabled descriptor");
196        assert_eq!(commit_log.proto3_optional, Some(true));
197        assert_fields(
198            message("HandlesConfiguration"),
199            &[("min_name_length", 1, Uint32, Optional, None)],
200        );
201        assert_fields(
202            message("GetConfigurationResponse"),
203            &[
204                ("identifier", 1, String, Optional, None),
205                ("server_version", 2, String, Optional, None),
206                ("min_libxmtp_version", 3, String, Optional, None),
207                (
208                    "auth",
209                    4,
210                    Message,
211                    Optional,
212                    Some(".xmtp.backend.v1.AuthConfiguration"),
213                ),
214                (
215                    "retention",
216                    5,
217                    Message,
218                    Optional,
219                    Some(".xmtp.backend.v1.RetentionConfiguration"),
220                ),
221                (
222                    "limits",
223                    6,
224                    Message,
225                    Optional,
226                    Some(".xmtp.backend.v1.LimitsConfiguration"),
227                ),
228                (
229                    "mls",
230                    7,
231                    Message,
232                    Optional,
233                    Some(".xmtp.backend.v1.MlsConfiguration"),
234                ),
235                ("smart_contract_wallet_chains", 8, String, Repeated, None),
236                (
237                    "attachments",
238                    9,
239                    Message,
240                    Optional,
241                    Some(".xmtp.backend.v1.AttachmentsConfiguration"),
242                ),
243                (
244                    "handles",
245                    11,
246                    Message,
247                    Optional,
248                    Some(".xmtp.backend.v1.HandlesConfiguration"),
249                ),
250            ],
251        );
252    }
253
254    /// The publish rejection reasons keep their numbers, the three handle
255    /// reasons included, so a client reads each reason the backend sends.
256    // verifies: API-210
257    #[xmtp_common::test(unwrap_try = true)]
258    fn publish_error_reasons_match_the_wire_contract() {
259        let descriptors = prost_types::FileDescriptorSet::decode(crate::FILE_DESCRIPTOR_SET)?;
260        let reason = descriptors
261            .file
262            .iter()
263            .filter(|file| file.package.as_deref() == Some("xmtp.backend.v1"))
264            .flat_map(|file| &file.message_type)
265            .find(|message| message.name.as_deref() == Some("PublishError"))
266            .and_then(|message| {
267                message
268                    .enum_type
269                    .iter()
270                    .find(|kind| kind.name.as_deref() == Some("Reason"))
271            })
272            .expect("PublishError.Reason descriptor");
273        let values: Vec<_> = reason
274            .value
275            .iter()
276            .map(|value| (value.name.as_deref().unwrap_or_default(), value.number()))
277            .collect();
278        assert_eq!(
279            values,
280            [
281                ("REASON_UNSPECIFIED", 0),
282                ("REASON_MALFORMED_PAYLOAD", 1),
283                ("REASON_INVALID_KEY_PACKAGE", 2),
284                ("REASON_INVALID_IDENTITY_UPDATE", 3),
285                ("REASON_INVALID_SIGNATURE", 4),
286                ("REASON_TOO_LARGE", 5),
287                ("REASON_HANDLE_TAKEN", 6),
288                ("REASON_HANDLE_NOT_ALLOWED", 7),
289                ("REASON_HANDLE_TOO_SOON", 8),
290            ]
291        );
292    }
293
294    // verifies: ATCH-020
295    #[xmtp_common::test(unwrap_try = true)]
296    fn create_upload_method_matches_wire_contract() {
297        let descriptors = prost_types::FileDescriptorSet::decode(crate::FILE_DESCRIPTOR_SET)?;
298        let file = descriptors
299            .file
300            .iter()
301            .find(|file| {
302                file.package.as_deref() == Some("xmtp.backend.v1")
303                    && file
304                        .service
305                        .iter()
306                        .any(|service| service.name.as_deref() == Some("AttachmentService"))
307            })
308            .expect("backend AttachmentService file descriptor");
309        let service = file
310            .service
311            .iter()
312            .find(|service| service.name.as_deref() == Some("AttachmentService"))
313            .expect("AttachmentService descriptor");
314        let method = service
315            .method
316            .iter()
317            .find(|method| method.name.as_deref() == Some("CreateUpload"))
318            .expect("CreateUpload descriptor");
319
320        assert_eq!(
321            format!(
322                "{}.{}.{}",
323                file.package.as_deref().expect("package name"),
324                service.name.as_deref().expect("service name"),
325                method.name.as_deref().expect("method name")
326            ),
327            "xmtp.backend.v1.AttachmentService.CreateUpload"
328        );
329        assert_eq!(
330            method.input_type.as_deref(),
331            Some(".xmtp.backend.v1.CreateUploadRequest")
332        );
333        assert_eq!(
334            method.output_type.as_deref(),
335            Some(".xmtp.backend.v1.CreateUploadResponse")
336        );
337        assert!(!method.client_streaming.unwrap_or(false));
338        assert!(!method.server_streaming.unwrap_or(false));
339    }
340}