1use crate::auth::crypto::{EncryptedData, KdfParams};
14use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
15use serde::{Deserialize, Serialize};
16use std::collections::HashMap;
17use thiserror::Error;
18
19#[derive(Debug, Error)]
20pub enum FormatError {
21 #[error("Invalid magic bytes")]
22 InvalidMagic,
23 #[error("Unsupported format version: {0}")]
24 UnsupportedVersion(u32),
25 #[error("IO error: {0}")]
26 Io(#[from] std::io::Error),
27 #[error("JSON error: {0}")]
28 Json(#[from] serde_json::Error),
29 #[error("Invalid format: {0}")]
30 InvalidFormat(String),
31}
32
33pub type Result<T> = std::result::Result<T, FormatError>;
34
35const MAGIC: &[u8; 8] = b"SLACKCLI";
36const CURRENT_VERSION: u32 = 1;
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct ExportProfile {
41 pub team_id: String,
42 pub user_id: String,
43 pub team_name: Option<String>,
44 pub user_name: Option<String>,
45 pub token: String,
46 #[serde(skip_serializing_if = "Option::is_none")]
48 pub client_id: Option<String>,
49 #[serde(skip_serializing_if = "Option::is_none")]
51 pub client_secret: Option<String>,
52 #[serde(skip_serializing_if = "Option::is_none")]
54 pub user_token: Option<String>,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct ExportPayload {
60 pub format_version: u32,
61 pub profiles: HashMap<String, ExportProfile>,
62
63 #[serde(flatten)]
65 #[serde(default)]
66 pub unknown_fields: HashMap<String, serde_json::Value>,
67}
68
69impl ExportPayload {
70 pub fn new() -> Self {
71 Self {
72 format_version: CURRENT_VERSION,
73 profiles: HashMap::new(),
74 unknown_fields: HashMap::new(),
75 }
76 }
77}
78
79impl Default for ExportPayload {
80 fn default() -> Self {
81 Self::new()
82 }
83}
84
85#[derive(Debug, Clone)]
87pub struct EncodedExport {
88 pub kdf_params: KdfParams,
89 pub encrypted_data: EncryptedData,
90}
91
92pub fn encode_export(
94 payload: &ExportPayload,
95 encrypted: &EncryptedData,
96 kdf_params: &KdfParams,
97) -> Result<Vec<u8>> {
98 let mut output = Vec::new();
99
100 output.extend_from_slice(MAGIC);
102
103 output.extend_from_slice(&payload.format_version.to_be_bytes());
105
106 let kdf_json = serde_json::json!({
108 "salt": BASE64.encode(&kdf_params.salt),
109 "memory_cost": kdf_params.memory_cost,
110 "time_cost": kdf_params.time_cost,
111 "parallelism": kdf_params.parallelism,
112 });
113 let kdf_bytes = serde_json::to_vec(&kdf_json)?;
114 output.extend_from_slice(&(kdf_bytes.len() as u32).to_be_bytes());
115 output.extend_from_slice(&kdf_bytes);
116
117 output.extend_from_slice(&(encrypted.nonce.len() as u32).to_be_bytes());
119 output.extend_from_slice(&encrypted.nonce);
120
121 output.extend_from_slice(&(encrypted.ciphertext.len() as u32).to_be_bytes());
123 output.extend_from_slice(&encrypted.ciphertext);
124
125 Ok(output)
126}
127
128pub fn decode_export(data: &[u8]) -> Result<EncodedExport> {
130 if data.len() < 8 {
131 return Err(FormatError::InvalidFormat("File too small".to_string()));
132 }
133
134 let mut cursor = 0;
135
136 if &data[cursor..cursor + 8] != MAGIC {
138 return Err(FormatError::InvalidMagic);
139 }
140 cursor += 8;
141
142 if data.len() < cursor + 4 {
144 return Err(FormatError::InvalidFormat(
145 "Missing format version".to_string(),
146 ));
147 }
148 let version = u32::from_be_bytes([
149 data[cursor],
150 data[cursor + 1],
151 data[cursor + 2],
152 data[cursor + 3],
153 ]);
154 cursor += 4;
155
156 if version != CURRENT_VERSION {
157 return Err(FormatError::UnsupportedVersion(version));
158 }
159
160 if data.len() < cursor + 4 {
162 return Err(FormatError::InvalidFormat(
163 "Missing KDF params length".to_string(),
164 ));
165 }
166 let kdf_len = u32::from_be_bytes([
167 data[cursor],
168 data[cursor + 1],
169 data[cursor + 2],
170 data[cursor + 3],
171 ]) as usize;
172 cursor += 4;
173
174 if data.len() < cursor + kdf_len {
175 return Err(FormatError::InvalidFormat(
176 "Missing KDF params data".to_string(),
177 ));
178 }
179 let kdf_json: serde_json::Value = serde_json::from_slice(&data[cursor..cursor + kdf_len])?;
180 cursor += kdf_len;
181
182 let salt = BASE64
183 .decode(
184 kdf_json["salt"]
185 .as_str()
186 .ok_or_else(|| FormatError::InvalidFormat("Missing salt".to_string()))?,
187 )
188 .map_err(|e| FormatError::InvalidFormat(format!("Invalid salt: {}", e)))?;
189
190 let kdf_params = KdfParams {
191 salt,
192 memory_cost: kdf_json["memory_cost"]
193 .as_u64()
194 .ok_or_else(|| FormatError::InvalidFormat("Missing memory_cost".to_string()))?
195 as u32,
196 time_cost: kdf_json["time_cost"]
197 .as_u64()
198 .ok_or_else(|| FormatError::InvalidFormat("Missing time_cost".to_string()))?
199 as u32,
200 parallelism: kdf_json["parallelism"]
201 .as_u64()
202 .ok_or_else(|| FormatError::InvalidFormat("Missing parallelism".to_string()))?
203 as u32,
204 };
205
206 if data.len() < cursor + 4 {
208 return Err(FormatError::InvalidFormat(
209 "Missing nonce length".to_string(),
210 ));
211 }
212 let nonce_len = u32::from_be_bytes([
213 data[cursor],
214 data[cursor + 1],
215 data[cursor + 2],
216 data[cursor + 3],
217 ]) as usize;
218 cursor += 4;
219
220 if data.len() < cursor + nonce_len {
221 return Err(FormatError::InvalidFormat("Missing nonce data".to_string()));
222 }
223 let nonce = data[cursor..cursor + nonce_len].to_vec();
224 cursor += nonce_len;
225
226 if data.len() < cursor + 4 {
228 return Err(FormatError::InvalidFormat(
229 "Missing ciphertext length".to_string(),
230 ));
231 }
232 let ciphertext_len = u32::from_be_bytes([
233 data[cursor],
234 data[cursor + 1],
235 data[cursor + 2],
236 data[cursor + 3],
237 ]) as usize;
238 cursor += 4;
239
240 if data.len() < cursor + ciphertext_len {
241 return Err(FormatError::InvalidFormat(
242 "Missing ciphertext data".to_string(),
243 ));
244 }
245 let ciphertext = data[cursor..cursor + ciphertext_len].to_vec();
246
247 Ok(EncodedExport {
248 kdf_params,
249 encrypted_data: EncryptedData { nonce, ciphertext },
250 })
251}
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256 use crate::auth::crypto;
257
258 #[test]
259 fn test_export_payload_serialization() {
260 let mut payload = ExportPayload::new();
261 payload.profiles.insert(
262 "default".to_string(),
263 ExportProfile {
264 team_id: "T123".to_string(),
265 user_id: "U456".to_string(),
266 team_name: Some("Test Team".to_string()),
267 user_name: Some("Test User".to_string()),
268 token: "xoxb-test-token".to_string(),
269 client_id: None,
270 client_secret: None,
271 user_token: None,
272 },
273 );
274
275 let json = serde_json::to_string(&payload).unwrap();
276 let deserialized: ExportPayload = serde_json::from_str(&json).unwrap();
277
278 assert_eq!(payload.format_version, deserialized.format_version);
279 assert_eq!(payload.profiles.len(), deserialized.profiles.len());
280 }
281
282 #[test]
283 fn test_export_payload_unknown_fields() {
284 let json = r#"{
285 "format_version": 1,
286 "profiles": {},
287 "future_field": "some_value"
288 }"#;
289
290 let payload: ExportPayload = serde_json::from_str(json).unwrap();
291 assert_eq!(payload.format_version, 1);
292 assert!(payload.unknown_fields.contains_key("future_field"));
293 }
294
295 #[test]
296 fn test_encode_decode_round_trip() {
297 let payload = ExportPayload::new();
298 let passphrase = "test_password";
299
300 let kdf_params = KdfParams {
302 salt: crypto::generate_salt(),
303 ..Default::default()
304 };
305
306 let payload_json = serde_json::to_vec(&payload).unwrap();
308 let key = crypto::derive_key(passphrase, &kdf_params).unwrap();
309 let encrypted = crypto::encrypt(&payload_json, &key).unwrap();
310
311 let encoded = encode_export(&payload, &encrypted, &kdf_params).unwrap();
313
314 let decoded = decode_export(&encoded).unwrap();
316
317 assert_eq!(kdf_params.salt, decoded.kdf_params.salt);
319 assert_eq!(kdf_params.memory_cost, decoded.kdf_params.memory_cost);
320 assert_eq!(kdf_params.time_cost, decoded.kdf_params.time_cost);
321 assert_eq!(kdf_params.parallelism, decoded.kdf_params.parallelism);
322
323 assert_eq!(encrypted.nonce, decoded.encrypted_data.nonce);
325 assert_eq!(encrypted.ciphertext, decoded.encrypted_data.ciphertext);
326 }
327
328 #[test]
329 fn test_decode_invalid_magic() {
330 let data = b"INVALID_DATA";
331 let result = decode_export(data);
332 assert!(result.is_err());
333 assert!(matches!(result.unwrap_err(), FormatError::InvalidMagic));
334 }
335
336 #[test]
337 fn test_decode_unsupported_version() {
338 let mut data = Vec::new();
339 data.extend_from_slice(MAGIC);
340 data.extend_from_slice(&999u32.to_be_bytes()); let result = decode_export(&data);
343 assert!(result.is_err());
344 assert!(matches!(
345 result.unwrap_err(),
346 FormatError::UnsupportedVersion(999)
347 ));
348 }
349}