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