Skip to main content

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