1use super::{BACKUP_VERSION, OptionsToSave, export_stream::BatchExportStream};
2use crate::archive_options::ArchiveOptions;
3use crate::{NONCE_SIZE, util::GenericArrayExt};
4use aes_gcm::{Aes256Gcm, AesGcm, KeyInit, aead::Aead, aes::Aes256};
5use async_compression::futures::write::ZstdEncoder;
6use futures::{Stream, pin_mut, ready, task::Context};
7use futures_util::{AsyncRead, AsyncWriteExt};
8use pin_project::pin_project;
9use prost::Message;
10#[allow(deprecated)]
11use sha2::digest::{generic_array::GenericArray, typenum};
12use std::{future::Future, io, pin::Pin, sync::Arc, task::Poll};
13use xmtp_db::prelude::*;
14use xmtp_proto::xmtp::device_sync::{BackupElement, BackupMetadataSave, backup_element::Element};
15
16#[cfg(not(target_arch = "wasm32"))]
17mod file_export;
18
19#[pin_project]
20pub struct ArchiveExporter {
21 stage: Stage,
22 metadata: BackupMetadataSave,
23 #[pin]
24 stream: BatchExportStream,
25 position: usize,
26 zstd_encoder: ZstdEncoder<Vec<u8>>,
27 encoder_finished: bool,
28
29 cipher: AesGcm<Aes256, typenum::U12, typenum::U16>,
30 nonce: GenericArray<u8, typenum::U12>,
31
32 nonce_buffer: Vec<u8>,
34}
35
36#[derive(Default)]
37pub(super) enum Stage {
38 #[default]
39 Nonce,
40 Metadata,
41 Elements,
42}
43
44impl ArchiveExporter {
45 #[cfg(not(target_arch = "wasm32"))]
46 pub async fn export_to_file<D>(
47 options: ArchiveOptions,
48 db: D,
49 path: impl AsRef<std::path::Path>,
50 key: &[u8],
51 ) -> Result<BackupMetadataSave, crate::ArchiveError>
52 where
53 D: DbQuery + 'static,
54 {
55 let mut exporter = Self::new(options, db, key);
56 exporter.write_to_file(path).await?;
57
58 Ok(exporter.metadata)
59 }
60
61 pub fn new<D>(options: ArchiveOptions, db: D, key: &[u8]) -> Self
62 where
63 D: DbQuery + 'static,
64 {
65 let mut nonce_buffer = BACKUP_VERSION.to_le_bytes().to_vec();
66 let nonce = xmtp_common::rand_array::<NONCE_SIZE>();
67 nonce_buffer.extend_from_slice(&nonce);
68
69 Self {
70 position: 0,
71 stage: Stage::default(),
72 stream: BatchExportStream::new(&options, Arc::new(db)),
73 metadata: BackupMetadataSave::from_options(options),
74 zstd_encoder: ZstdEncoder::new(Vec::new()),
75 encoder_finished: false,
76
77 #[allow(deprecated)]
78 cipher: Aes256Gcm::new(GenericArray::from_slice(key)),
79 #[allow(deprecated)]
80 nonce: GenericArray::clone_from_slice(&nonce),
81 nonce_buffer,
82 }
83 }
84
85 pub fn metadata(&self) -> &BackupMetadataSave {
86 &self.metadata
87 }
88}
89
90impl AsyncRead for ArchiveExporter {
97 fn poll_read(
99 self: Pin<&mut Self>,
100 cx: &mut Context<'_>,
101 buf: &mut [u8],
102 ) -> Poll<io::Result<usize>> {
103 let mut this = self.project();
104 loop {
105 if matches!(this.stage, Stage::Nonce) {
107 let amount = this.nonce_buffer.len().min(buf.len());
108 let nonce_bytes: Vec<_> = this.nonce_buffer.drain(..amount).collect();
109 buf[..amount].copy_from_slice(&nonce_bytes);
110
111 if this.nonce_buffer.is_empty() {
112 *this.stage = Stage::Metadata;
113 }
114 return Poll::Ready(Ok(amount));
115 }
116
117 {
118 let buffer_inner = this.zstd_encoder.get_ref();
120 if *this.position < buffer_inner.len() {
121 let available = &buffer_inner[*this.position..];
122 let amount = available.len().min(buf.len());
123 buf[..amount].copy_from_slice(&available[..amount]);
124 *this.position += amount;
125
126 return Poll::Ready(Ok(amount));
127 }
128 }
129
130 *this.position = 0;
132 this.zstd_encoder.get_mut().clear();
133
134 while this.zstd_encoder.get_ref().len() < 8_000 {
136 let element = match this.stage {
137 Stage::Nonce => {
138 unreachable!("Nonce should not be the stage here.");
140 }
141 Stage::Metadata => {
142 *this.stage = Stage::Elements;
143 BackupElement {
144 element: Some(Element::Metadata(this.metadata.clone())),
145 }
146 .encode_to_vec()
147 }
148 Stage::Elements => match ready!(this.stream.as_mut().poll_next(cx)) {
149 Some(element) => element
150 .map_err(|err| io::Error::other(err.to_string()))?
151 .encode_to_vec(),
152 None => {
153 if !*this.encoder_finished {
154 *this.encoder_finished = true;
155 let fut = this.zstd_encoder.close();
156 pin_mut!(fut);
157 let _ = fut.poll(cx)?;
158 }
159 break;
160 }
161 },
162 };
163
164 let mut element = this
165 .cipher
166 .encrypt(this.nonce, &*element)
167 .expect("Encryption should always work");
168 let mut bytes = (element.len() as u32).to_le_bytes().to_vec();
169 bytes.append(&mut element);
170 this.nonce.increment();
171
172 let fut = this.zstd_encoder.write(&bytes);
173 pin_mut!(fut);
174 match fut.poll(cx) {
175 Poll::Ready(Ok(_amt)) => {}
176 Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
177 Poll::Pending => return Poll::Pending,
178 }
179 }
180
181 if !*this.encoder_finished {
183 let fut = this.zstd_encoder.flush();
184 pin_mut!(fut);
185 let _ = fut.poll(cx)?;
186 }
187
188 if this.zstd_encoder.get_ref().is_empty() {
189 return Poll::Ready(Ok(0));
190 }
191 }
192 }
193}