xmtp_proto/types/
welcome_message.rs

1use crate::types::{Cursor, InstallationId};
2use crate::{
3    ConversionError,
4    xmtp::mls::message_contents::{
5        WelcomePointeeEncryptionAeadType, WelcomePointerWrapperAlgorithm, WelcomeWrapperAlgorithm,
6    },
7};
8use chrono::Utc;
9use derive_builder::Builder;
10use prost::Message;
11
12/// Welcome Message from the network
13#[derive(Clone, Builder, Debug)]
14#[builder(setter(into), build_fn(error = "ConversionError"))]
15pub struct WelcomeMessage {
16    /// cursor of this message
17    pub cursor: Cursor,
18    /// server timestamp indicating when this message was created
19    pub created_ns: chrono::DateTime<Utc>,
20    /// Variant of the welcome message
21    pub variant: WelcomeMessageType,
22}
23
24impl WelcomeMessage {
25    pub fn builder() -> WelcomeMessageBuilder {
26        WelcomeMessageBuilder::default()
27    }
28    pub fn as_v1(&self) -> Option<&WelcomeMessageV1> {
29        match &self.variant {
30            WelcomeMessageType::V1(v1) => Some(v1),
31            _ => None,
32        }
33    }
34}
35
36impl WelcomeMessage {
37    pub fn sequence_id(&self) -> u64 {
38        self.cursor.sequence_id
39    }
40
41    pub fn originator_id(&self) -> u32 {
42        self.cursor.originator_id
43    }
44
45    pub fn timestamp(&self) -> i64 {
46        self.created_ns
47            .timestamp_nanos_opt()
48            .expect("timestamp out of range for i64, are we in 2262 A.D?")
49    }
50
51    pub fn resuming(&self) -> bool {
52        matches!(
53            &self.variant,
54            WelcomeMessageType::DecryptedWelcomePointer(_)
55        )
56    }
57}
58
59#[derive(Clone, Debug)]
60pub enum WelcomeMessageType {
61    V1(WelcomeMessageV1),
62    WelcomePointer(WelcomePointer),
63    DecryptedWelcomePointer(DecryptedWelcomePointer),
64}
65
66impl From<WelcomeMessageV1> for WelcomeMessageType {
67    fn from(v1: WelcomeMessageV1) -> Self {
68        WelcomeMessageType::V1(v1)
69    }
70}
71
72impl From<WelcomePointer> for WelcomeMessageType {
73    fn from(pointer: WelcomePointer) -> Self {
74        WelcomeMessageType::WelcomePointer(pointer)
75    }
76}
77
78impl From<DecryptedWelcomePointer> for WelcomeMessageType {
79    fn from(pointer: DecryptedWelcomePointer) -> Self {
80        WelcomeMessageType::DecryptedWelcomePointer(pointer)
81    }
82}
83
84#[derive(Clone, Builder, Debug)]
85#[builder(build_fn(error = "ConversionError"))]
86pub struct WelcomeMessageV1 {
87    // Installation key the welcome was sent to
88    pub installation_key: InstallationId,
89    // HPKE public key used to encrypt the welcome
90    pub hpke_public_key: Vec<u8>,
91    // Wrapper algorithm used to encrypt the welcome
92    pub wrapper_algorithm: WelcomeWrapperAlgorithm,
93    // Encrypted welcome message payload
94    pub data: Vec<u8>,
95    // Encrypted welcome metadata
96    pub welcome_metadata: Vec<u8>,
97}
98
99impl WelcomeMessageV1 {
100    pub fn builder() -> WelcomeMessageV1Builder {
101        WelcomeMessageV1Builder::default()
102    }
103}
104
105#[derive(Clone, Builder, Debug)]
106#[builder(build_fn(error = "ConversionError"))]
107pub struct WelcomePointer {
108    // Installation key the welcome pointer was sent to
109    pub installation_key: InstallationId,
110    // HPKE public key used to encrypt the welcome pointer
111    pub hpke_public_key: Vec<u8>,
112    // Wrapper algorithm used to encrypt the welcome pointer (Only post quantum compatible algorithms are allowed)
113    pub wrapper_algorithm: WelcomePointerWrapperAlgorithm,
114    // Encrypted welcome pointer data
115    pub welcome_pointer: Vec<u8>,
116}
117
118impl WelcomePointer {
119    pub fn builder() -> WelcomePointerBuilder {
120        WelcomePointerBuilder::default()
121    }
122}
123
124#[derive(Clone, Builder, Debug)]
125#[builder(build_fn(error = "ConversionError"))]
126pub struct DecryptedWelcomePointer {
127    // Topic the welcome pointee was sent to
128    pub destination: InstallationId,
129    // AEAD type used to encrypt the welcome pointee
130    pub aead_type: WelcomePointeeEncryptionAeadType,
131    // Encryption key used to encrypt the welcome pointee. Length MUST match the aead_type.
132    pub encryption_key: Vec<u8>,
133    // Nonce used to encrypt the welcome pointee data. Length MUST match the aead_type.
134    pub data_nonce: Vec<u8>,
135    // Nonce used to encrypt the welcome pointee metadata. Length MUST match the aead_type.
136    pub welcome_metadata_nonce: Vec<u8>,
137}
138
139impl DecryptedWelcomePointer {
140    pub fn builder() -> DecryptedWelcomePointerBuilder {
141        DecryptedWelcomePointerBuilder::default()
142    }
143    pub fn decode(data: &[u8]) -> Result<Self, ConversionError> {
144        let wp = crate::xmtp::mls::message_contents::WelcomePointer::decode(data)?;
145        let wp = match wp.version {
146            Some(
147                crate::xmtp::mls::message_contents::welcome_pointer::Version::WelcomeV1Pointer(v1),
148            ) => v1,
149            None => {
150                return Err(ConversionError::InvalidValue {
151                    item: "WelcomePointer",
152                    expected: "WelcomeV1Pointer",
153                    got: "None".into(),
154                });
155            }
156        };
157        Ok(Self {
158            destination: wp.destination.try_into()?,
159            aead_type: wp.aead_type.try_into()?,
160            encryption_key: wp.encryption_key,
161            data_nonce: wp.data_nonce,
162            welcome_metadata_nonce: wp.welcome_metadata_nonce,
163        })
164    }
165    pub fn to_proto(self) -> crate::xmtp::mls::message_contents::WelcomePointer {
166        crate::xmtp::mls::message_contents::WelcomePointer {
167            version: Some(
168                crate::xmtp::mls::message_contents::welcome_pointer::Version::WelcomeV1Pointer(
169                    crate::xmtp::mls::message_contents::welcome_pointer::WelcomeV1Pointer {
170                        destination: self.destination.to_vec(),
171                        aead_type: self.aead_type.into(),
172                        encryption_key: self.encryption_key,
173                        data_nonce: self.data_nonce,
174                        welcome_metadata_nonce: self.welcome_metadata_nonce,
175                    },
176                ),
177            ),
178        }
179    }
180}
181
182impl TryFrom<crate::xmtp::mls::message_contents::WelcomePointer> for DecryptedWelcomePointer {
183    type Error = ConversionError;
184    fn try_from(
185        value: crate::xmtp::mls::message_contents::WelcomePointer,
186    ) -> Result<Self, Self::Error> {
187        let wp = match value.version {
188            Some(
189                crate::xmtp::mls::message_contents::welcome_pointer::Version::WelcomeV1Pointer(v1),
190            ) => v1,
191            None => {
192                return Err(ConversionError::InvalidValue {
193                    item: "WelcomePointer",
194                    expected: "WelcomeV1Pointer",
195                    got: "None".into(),
196                });
197            }
198        };
199        Ok(Self {
200            destination: wp.destination.try_into()?,
201            aead_type: wp.aead_type.try_into()?,
202            encryption_key: wp.encryption_key,
203            data_nonce: wp.data_nonce,
204            welcome_metadata_nonce: wp.welcome_metadata_nonce,
205        })
206    }
207}
208
209#[cfg(any(test, feature = "test-utils"))]
210impl xmtp_common::Generate for WelcomeMessage {
211    fn generate() -> Self {
212        Self {
213            cursor: Cursor::generate(),
214            created_ns: chrono::DateTime::from_timestamp_nanos(xmtp_common::rand_i64()),
215            variant: WelcomeMessageV1 {
216                installation_key: xmtp_common::rand_array::<32>().into(),
217                data: xmtp_common::rand_vec::<16>(),
218                hpke_public_key: xmtp_common::rand_vec::<16>(),
219                wrapper_algorithm: WelcomeWrapperAlgorithm::Curve25519,
220                welcome_metadata: xmtp_common::rand_vec::<16>(),
221            }
222            .into(),
223        }
224    }
225}
226
227#[cfg(test)]
228mod test {
229    use super::*;
230    use rstest::rstest;
231    use xmtp_common::Generate;
232
233    #[rstest]
234    #[case(Cursor::new(123, 456u32), 123, 456u32)]
235    #[case(Cursor::new(0, 0u32), 0, 0u32)]
236    #[case(Cursor::new(u64::MAX, u32::MAX), u64::MAX, u32::MAX)]
237    #[xmtp_common::test]
238    async fn test_accessor_methods(
239        #[case] cursor: Cursor,
240        #[case] expected_seq: u64,
241        #[case] expected_orig: u32,
242    ) {
243        use xmtp_common::Generate;
244
245        let mut welcome_message = WelcomeMessage::generate();
246        welcome_message.cursor = cursor;
247        assert_eq!(welcome_message.sequence_id(), expected_seq);
248        assert_eq!(welcome_message.originator_id(), expected_orig);
249    }
250
251    #[xmtp_common::test]
252    async fn test_timestamp() {
253        let test_time = chrono::Utc::now();
254        let mut welcome_message = WelcomeMessage::generate();
255        welcome_message.created_ns = test_time;
256        assert_eq!(
257            welcome_message.timestamp(),
258            test_time.timestamp_nanos_opt().unwrap()
259        );
260    }
261}