xmtp_proto/types/
welcome_message.rs1use 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.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 pub installation_key: InstallationId,
71 pub hpke_public_key: Vec<u8>,
73 pub wrapper_algorithm: WelcomeWrapperAlgorithm,
75 pub data: Vec<u8>,
77 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 pub installation_key: InstallationId,
92 pub hpke_public_key: Vec<u8>,
94 pub wrapper_algorithm: WelcomePointerWrapperAlgorithm,
96 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 pub destination: InstallationId,
111 pub aead_type: WelcomePointeeEncryptionAeadType,
113 pub encryption_key: Vec<u8>,
115 pub data_nonce: Vec<u8>,
117 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}