1use crate::archive_options::BackupElementSelection;
13use aes_gcm::{Aes256Gcm, KeyInit};
14pub use importer::ArchiveImporter;
15use thiserror::Error;
16use xmtp_common::{ErrorCode, RetryableError};
17use xmtp_db::{ConnectionError, StorageError, diesel, sql_key_store::SqlKeyStoreError};
18use xmtp_mls_common::{
19 group_metadata::GroupMetadataError, group_mutable_metadata::GroupMutableMetadataError,
20};
21use xmtp_proto::{types::GroupId, xmtp::device_sync::BackupMetadataSave};
22
23pub const ENC_KEY_SIZE: usize = 32; pub const NONCE_SIZE: usize = 12; pub const TAG_SIZE: usize = 16; pub const BACKUP_VERSION: u16 = 0;
29
30pub mod archive_options;
31pub mod exporter;
32pub mod importer;
33mod snapshot;
34mod util;
35
36#[derive(Debug, Error, ErrorCode)]
38pub enum ArchiveError {
39 #[error("Unsupported archive version {0}; this client reads version {BACKUP_VERSION}")]
43 UnsupportedVersion(u16),
44 #[error("Missing metadata")]
48 MissingMetadata,
49 #[error("Invalid archive frame: {0}")]
53 InvalidFrame(&'static str),
54 #[error("AES-GCM encryption error")]
58 AesGcm(#[from] aes_gcm::Error),
59 #[error("IO error: {0}")]
63 IO(#[from] std::io::Error),
64 #[error(transparent)]
68 Decode(#[from] prost::DecodeError),
69 #[error(transparent)]
70 #[error_code(inherit)]
71 Storage(#[from] StorageError),
72 #[error("archive key must be {ENC_KEY_SIZE} bytes, got {0}")]
77 InvalidKeyLength(usize),
78 #[error("group {group_id} cannot be exported: {source}")]
84 UnreadableGroup {
85 group_id: GroupId,
86 #[source]
87 source: UnreadableGroup,
88 },
89}
90
91impl RetryableError for ArchiveError {
92 fn is_retryable(&self) -> bool {
97 use std::io::ErrorKind::*;
98 match self {
99 Self::IO(e) => matches!(e.kind(), Interrupted | WouldBlock | TimedOut),
100 Self::Storage(e) => e.is_retryable(),
101 Self::UnreadableGroup {
102 source: UnreadableGroup::State(e),
103 ..
104 } => e.is_retryable(),
105 _ => false,
106 }
107 }
108}
109
110impl From<ConnectionError> for ArchiveError {
111 fn from(e: ConnectionError) -> Self {
112 Self::Storage(e.into())
113 }
114}
115
116impl From<diesel::result::Error> for ArchiveError {
117 fn from(e: diesel::result::Error) -> Self {
118 Self::Storage(e.into())
119 }
120}
121
122#[derive(Debug, Error)]
124pub enum UnreadableGroup {
125 #[error("no MLS group state")]
126 MissingState,
127 #[error(transparent)]
128 State(#[from] SqlKeyStoreError),
129 #[error(transparent)]
130 Metadata(#[from] GroupMetadataError),
131 #[error(transparent)]
132 MutableMetadata(#[from] GroupMutableMetadataError),
133}
134
135pub fn check_key(key: &[u8]) -> Result<(), ArchiveError> {
140 match key.len() {
141 ENC_KEY_SIZE => Ok(()),
142 len => Err(ArchiveError::InvalidKeyLength(len)),
143 }
144}
145
146fn cipher(key: &[u8]) -> Result<Aes256Gcm, ArchiveError> {
148 Aes256Gcm::new_from_slice(key).map_err(|_| ArchiveError::InvalidKeyLength(key.len()))
149}
150
151#[derive(Default)]
152pub struct BackupMetadata {
153 pub backup_version: u16,
154 pub elements: Vec<BackupElementSelection>,
155 pub exported_at_ns: i64,
156 pub start_ns: Option<i64>,
157 pub end_ns: Option<i64>,
158}
159
160impl BackupMetadata {
161 pub fn from_metadata_save(save: BackupMetadataSave, backup_version: u16) -> Self {
162 Self {
163 elements: save.elements().map(Into::into).collect(),
164 end_ns: save.end_ns,
165 start_ns: save.start_ns,
166 exported_at_ns: save.exported_at_ns,
167 backup_version,
168 }
169 }
170}