Skip to main content

xmtp_content_types/
multi_remote_attachment.rs

1use std::collections::HashMap;
2
3use crate::{CodecError, ContentCodec};
4use prost::Message;
5use xmtp_proto::xmtp::mls::message_contents::{
6    ContentTypeId, EncodedContent, content_types::MultiRemoteAttachment,
7};
8
9pub struct MultiRemoteAttachmentCodec {}
10
11impl MultiRemoteAttachmentCodec {
12    const AUTHORITY_ID: &'static str = "xmtp.org";
13    pub const TYPE_ID: &'static str = "multiRemoteStaticAttachment";
14    pub const MAJOR_VERSION: u32 = 1;
15    pub const MINOR_VERSION: u32 = 0;
16}
17
18impl MultiRemoteAttachmentCodec {
19    fn fallback(_: &MultiRemoteAttachment) -> Option<String> {
20        Some(
21            "Can't display this content. This app doesn't support multiple remote attachments."
22                .to_string(),
23        )
24    }
25}
26
27impl ContentCodec<MultiRemoteAttachment> for MultiRemoteAttachmentCodec {
28    fn content_type() -> ContentTypeId {
29        ContentTypeId {
30            authority_id: MultiRemoteAttachmentCodec::AUTHORITY_ID.to_string(),
31            type_id: MultiRemoteAttachmentCodec::TYPE_ID.to_string(),
32            version_major: MultiRemoteAttachmentCodec::MAJOR_VERSION,
33            version_minor: MultiRemoteAttachmentCodec::MINOR_VERSION,
34        }
35    }
36
37    fn encode(data: MultiRemoteAttachment) -> Result<EncodedContent, CodecError> {
38        for attachment in &data.attachments {
39            crate::remote_attachment::validate_remote_attachment(attachment)
40                .map_err(|reason| CodecError::Encode(reason.into()))?;
41        }
42        let mut buf = Vec::new();
43        data.encode(&mut buf)
44            .map_err(|e| CodecError::Encode(e.to_string()))?;
45
46        Ok(EncodedContent {
47            r#type: Some(MultiRemoteAttachmentCodec::content_type()),
48            parameters: HashMap::new(),
49            fallback: Self::fallback(&data),
50            compression: None,
51            content: buf,
52        })
53    }
54
55    fn decode(content: EncodedContent) -> Result<MultiRemoteAttachment, CodecError> {
56        let mut decoded = MultiRemoteAttachment::decode(content.content.as_slice())
57            .map_err(|e| CodecError::Decode(e.to_string()))?;
58
59        for attachment in &mut decoded.attachments {
60            crate::remote_attachment::normalize_decoded_scheme(attachment);
61            crate::remote_attachment::validate_remote_attachment(attachment)
62                .map_err(|reason| CodecError::Decode(reason.into()))?;
63        }
64        Ok(decoded)
65    }
66
67    fn should_push() -> bool {
68        true
69    }
70}
71
72#[cfg(test)]
73pub(crate) mod tests {
74    use xmtp_proto::xmtp::mls::message_contents::content_types::RemoteAttachmentInfo;
75
76    use super::*;
77
78    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
79    #[cfg_attr(not(target_arch = "wasm32"), test)]
80    // verifies: CTYPE-007
81    fn test_encode_decode() {
82        let attachment_info_1 = RemoteAttachmentInfo {
83            content_digest: "ab".repeat(32),
84            secret: vec![0; 32],
85            nonce: vec![0; 12],
86            salt: vec![0; 32],
87            scheme: "https://".to_string(),
88            url: "https://example.com/attachment".to_string(),
89            content_length: Some(1000),
90            filename: Some("attachment_1.jpg".to_string()),
91        };
92        let attachment_info_2 = RemoteAttachmentInfo {
93            content_digest: "ab".repeat(32),
94            secret: vec![0; 32],
95            nonce: vec![0; 12],
96            salt: vec![0; 32],
97            scheme: "https://".to_string(),
98            url: "https://example.com/attachment".to_string(),
99            content_length: Some(1000),
100            filename: Some("attachment_2.jpg".to_string()),
101        };
102
103        // Store the filenames before moving the attachment_info structs
104        let filename_1 = attachment_info_1.filename.clone();
105        let filename_2 = attachment_info_2.filename.clone();
106
107        let new_multi_remote_attachment_data: MultiRemoteAttachment = MultiRemoteAttachment {
108            attachments: vec![attachment_info_1.clone(), attachment_info_2.clone()],
109        };
110
111        let encoded = MultiRemoteAttachmentCodec::encode(new_multi_remote_attachment_data).unwrap();
112        assert_eq!(
113            encoded.clone().r#type.unwrap().type_id,
114            "multiRemoteStaticAttachment"
115        );
116        assert!(!encoded.content.is_empty());
117
118        let decoded = MultiRemoteAttachmentCodec::decode(encoded).unwrap();
119        assert_eq!(decoded.attachments[0].filename, filename_1);
120        assert_eq!(decoded.attachments[1].filename, filename_2);
121        assert_eq!(decoded.attachments[0].content_length, Some(1000));
122        assert_eq!(decoded.attachments[1].content_length, Some(1000));
123    }
124}