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#[derive(Clone, Builder, Debug)]
14#[builder(setter(into), build_fn(error = "ConversionError"))]
15pub struct WelcomeMessage {
16 pub cursor: Cursor,
18 pub created_ns: chrono::DateTime<Utc>,
20 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 pub installation_key: InstallationId,
89 pub hpke_public_key: Vec<u8>,
91 pub wrapper_algorithm: WelcomeWrapperAlgorithm,
93 pub data: Vec<u8>,
95 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 pub installation_key: InstallationId,
110 pub hpke_public_key: Vec<u8>,
112 pub wrapper_algorithm: WelcomePointerWrapperAlgorithm,
114 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 pub destination: InstallationId,
129 pub aead_type: WelcomePointeeEncryptionAeadType,
131 pub encryption_key: Vec<u8>,
133 pub data_nonce: Vec<u8>,
135 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}