1use std::collections::{BTreeMap, HashMap};
11use std::path::Path;
12
13use crate::crypto::{self, AuthKey};
14use crate::qt::{QReader, QWriter};
15use crate::tdata::{APP_VERSION, FillRandom, encrypt, write_tdf};
16use crate::{Error, MapReadFailure, Result, tdf};
17
18const MAP_STREAM_FAILED: &str = "Could not stream data from mapData";
20
21pub const MAP_FILE: &str = "map";
23
24pub const LSK_DRAFT: u32 = 0x01;
26pub const LSK_DRAFT_POSITION: u32 = 0x02;
28pub const LSK_LEGACY_IMAGES: u32 = 0x03;
30pub const LSK_LEGACY_STICKER_IMAGES: u32 = 0x05;
32pub const LSK_LEGACY_AUDIOS: u32 = 0x06;
34pub const LSK_USER_SETTINGS: u32 = 0x09;
36pub const LSK_SELF_SERIALIZED: u32 = 0x15;
38pub const LSK_WEBVIEW_TOKENS: u32 = 0x19;
40pub const LSK_BOT_STORAGES: u32 = 0x1D;
42pub const LSK_WALLET_ENGINE_STORAGES: u32 = 0x21;
44
45pub const NEW_ACCOUNT_SETTINGS_KEY: u64 = 1_851_671_142_505_648_812;
48
49#[must_use]
53pub fn file_key_count(kind: u32) -> Option<usize> {
54 match kind {
55 0x04 | 0x07..=0x0F | 0x11..=0x13 | 0x18 | 0x1A..=0x1C | 0x1E..=0x20 => Some(1),
63 0x14 => Some(2),
65 0x10 => Some(4),
67 0x16 | 0x17 => Some(3),
69 _ => None,
70 }
71}
72
73#[derive(Debug, Clone, PartialEq, Eq)]
75pub enum MapEntry {
76 FileKeys {
78 kind: u32,
80 keys: Vec<u64>,
82 },
83 PeerKeys {
85 kind: u32,
87 pairs: Vec<(u64, u64)>,
89 },
90 LegacyLocations {
94 kind: u32,
96 items: Vec<(u64, u64, u64, i32)>,
98 },
99 SelfSerialized(Option<Vec<u8>>),
101 WebviewTokens {
103 bots: Option<Vec<u8>>,
105 other: Option<Vec<u8>>,
107 },
108 WalletEngineStorages(Vec<(u64, Option<String>)>),
111}
112
113#[derive(Debug, Clone, Default, PartialEq, Eq)]
115pub struct MapData {
116 pub version: u32,
118 pub legacy_salt: Option<Vec<u8>>,
120 pub legacy_key_encrypted: Option<Vec<u8>>,
122 pub entries: Vec<MapEntry>,
124 pub stopped_at: Option<u32>,
127}
128
129impl MapData {
130 pub fn parse_entries(data: &[u8]) -> Result<(Vec<MapEntry>, Option<u32>)> {
140 let mut reader = QReader::new(data);
141 let mut entries = Vec::new();
142 while !reader.is_empty() {
143 let Ok(kind) = reader.read_u32() else {
144 return Ok((entries, Some(0)));
145 };
146 match read_entry(&mut reader, kind)? {
147 Some(entry) => entries.push(entry),
148 None => return Ok((entries, Some(kind))),
149 }
150 }
151 Ok((entries, None))
152 }
153
154 pub fn read(dir: impl AsRef<Path>, local_key: &AuthKey) -> Result<Self> {
163 let file = tdf::read(dir, MAP_FILE).map_err(|_| Error::ReadMapDataFailed {
164 context: MapReadFailure::Open,
165 })?;
166 let mut reader = QReader::new(&file.payload);
167 let mut array = || {
168 reader
169 .read_bytearray()
170 .map_err(|_| Error::QDataStreamFailed {
171 context: Some(MAP_STREAM_FAILED),
172 })
173 };
174 let legacy_salt = array()?.map(<[u8]>::to_vec);
175 let legacy_key_encrypted = array()?.map(<[u8]>::to_vec);
176 let encrypted = array()?.unwrap_or_default();
177 let map =
178 crypto::decrypt_local(encrypted, local_key).map_err(|_| Error::ReadMapDataFailed {
179 context: MapReadFailure::Decrypt,
180 })?;
181 let (entries, stopped_at) = Self::parse_entries(&map)?;
182 Ok(Self {
183 version: file.version,
184 legacy_salt,
185 legacy_key_encrypted,
186 entries,
187 stopped_at,
188 })
189 }
190
191 #[must_use]
195 pub fn new_account() -> Self {
196 Self {
197 version: APP_VERSION,
198 legacy_salt: None,
199 legacy_key_encrypted: None,
200 entries: vec![MapEntry::FileKeys {
201 kind: LSK_USER_SETTINGS,
202 keys: vec![NEW_ACCOUNT_SETTINGS_KEY],
203 }],
204 stopped_at: None,
205 }
206 }
207
208 pub fn encode_entries(&self) -> Result<Vec<u8>> {
232 MapState::fold(&self.entries).encode()
233 }
234
235 pub(crate) fn write(
239 &self,
240 dir: &Path,
241 local_key: &AuthKey,
242 rng: &mut FillRandom<'_>,
243 ) -> Result<()> {
244 let entries = self.plaintext()?;
245 let mut payload = QWriter::new();
246 payload.write_bytearray(None)?;
247 payload.write_bytearray(None)?;
248 payload.write_bytearray(Some(&encrypt(&entries, local_key, rng)?))?;
249 write_tdf(dir, MAP_FILE, payload.into_bytes())
250 }
251
252 pub(crate) fn plaintext(&self) -> Result<Vec<u8>> {
255 let entries = self.encode_entries()?;
256 if entries.is_empty() {
257 return Err(Error::SaveFailed);
259 }
260 Ok(entries)
261 }
262}
263
264mod slot {
266 pub const PREFS: usize = 0;
267 pub const LOCATIONS: usize = 1;
268 pub const TRUSTED_BOTS: usize = 2;
269 pub const RECENT_STICKERS_OLD: usize = 3;
270 pub const INSTALLED_STICKERS: usize = 4;
271 pub const FEATURED_STICKERS: usize = 5;
272 pub const RECENT_STICKERS: usize = 6;
273 pub const FAVED_STICKERS: usize = 7;
274 pub const ARCHIVED_STICKERS: usize = 8;
275 pub const SAVED_GIFS: usize = 9;
276 pub const INSTALLED_MASKS: usize = 10;
277 pub const RECENT_MASKS: usize = 11;
278 pub const ARCHIVED_MASKS: usize = 12;
279 pub const INSTALLED_CUSTOM_EMOJI: usize = 13;
280 pub const FEATURED_CUSTOM_EMOJI: usize = 14;
281 pub const ARCHIVED_CUSTOM_EMOJI: usize = 15;
282 pub const SEARCH_SUGGESTIONS: usize = 16;
283 pub const BACKGROUND_DAY: usize = 17;
284 pub const BACKGROUND_NIGHT: usize = 18;
285 pub const USER_SETTINGS: usize = 19;
286 pub const RECENT_HASHTAGS: usize = 20;
287 pub const EXPORT_SETTINGS: usize = 21;
288 pub const ROUND_PLACEHOLDER: usize = 22;
289 pub const INLINE_BOTS_DOWNLOADS: usize = 23;
290 pub const MEDIA_PLAYBACK_POSITIONS: usize = 24;
291 pub const COUNT: usize = 25;
292}
293
294fn key_slots(kind: u32) -> &'static [usize] {
297 match kind {
298 0x1E => &[slot::PREFS],
299 0x04 => &[slot::LOCATIONS],
300 0x11 => &[slot::TRUSTED_BOTS],
301 0x07 => &[slot::RECENT_STICKERS_OLD],
302 0x08 => &[slot::BACKGROUND_DAY],
303 LSK_USER_SETTINGS => &[slot::USER_SETTINGS],
304 0x0A => &[slot::RECENT_HASHTAGS],
305 0x0B => &[slot::INSTALLED_STICKERS],
306 0x12 => &[slot::FAVED_STICKERS],
307 0x0F => &[slot::SAVED_GIFS],
308 0x13 => &[slot::EXPORT_SETTINGS],
309 0x18 => &[slot::SEARCH_SUGGESTIONS],
310 0x14 => &[slot::BACKGROUND_DAY, slot::BACKGROUND_NIGHT],
311 0x10 => &[
312 slot::INSTALLED_STICKERS,
313 slot::FEATURED_STICKERS,
314 slot::RECENT_STICKERS,
315 slot::ARCHIVED_STICKERS,
316 ],
317 0x16 => &[
318 slot::INSTALLED_MASKS,
319 slot::RECENT_MASKS,
320 slot::ARCHIVED_MASKS,
321 ],
322 0x17 => &[
323 slot::INSTALLED_CUSTOM_EMOJI,
324 slot::FEATURED_CUSTOM_EMOJI,
325 slot::ARCHIVED_CUSTOM_EMOJI,
326 ],
327 0x1A => &[slot::ROUND_PLACEHOLDER],
328 0x1B => &[slot::INLINE_BOTS_DOWNLOADS],
329 0x1C => &[slot::MEDIA_PLAYBACK_POSITIONS],
330 _ => &[],
331 }
332}
333
334const PEER_SERIALIZED_FLAG: u64 = 0x80 << 48;
336
337fn peer_from_serialized(serialized: u64) -> u64 {
340 if serialized & PEER_SERIALIZED_FLAG != 0 {
341 return serialized & !PEER_SERIALIZED_FLAG;
342 }
343 let bare = serialized & 0xFFFF_FFFF;
344 match serialized & 0xF_0000_0000 {
345 0 => bare,
346 0x1_0000_0000 => bare | (1 << 48),
347 0x2_0000_0000 => bare | (2 << 48),
348 0xF_0000_0000 => bare | (0x7F << 48),
349 _ => 0,
350 }
351}
352
353#[derive(Default)]
355struct PeerMap {
356 pairs: Vec<(u64, u64)>,
357 index: HashMap<u64, usize>,
358}
359
360impl PeerMap {
361 fn insert(&mut self, peer: u64, file_key: u64) {
362 if let Some(&at) = self.index.get(&peer) {
363 self.pairs[at].1 = file_key;
364 } else {
365 self.index.insert(peer, self.pairs.len());
366 self.pairs.push((peer, file_key));
367 }
368 }
369
370 fn encode(&self, writer: &mut QWriter, kind: u32) -> Result<()> {
371 if self.pairs.is_empty() {
372 return Ok(());
373 }
374 let count = u32::try_from(self.pairs.len())
375 .map_err(|_| Error::QDataStreamFailed { context: None })?;
376 writer.write_u32(kind);
377 writer.write_u32(count);
378 for &(peer, file_key) in &self.pairs {
379 writer.write_u64(file_key);
380 writer.write_u64(peer | PEER_SERIALIZED_FLAG);
381 }
382 Ok(())
383 }
384}
385
386struct MapState<'a> {
389 keys: [u64; slot::COUNT],
390 self_serialized: Option<&'a [u8]>,
391 drafts: PeerMap,
392 draft_positions: PeerMap,
393 bot_storages: PeerMap,
394 webview_bots: Option<&'a [u8]>,
396 webview_other: Option<&'a [u8]>,
397 wallet: BTreeMap<&'a str, u64>,
398}
399
400impl<'a> MapState<'a> {
401 fn fold(entries: &'a [MapEntry]) -> Self {
402 let mut state = Self {
403 keys: [0; slot::COUNT],
404 self_serialized: None,
405 drafts: PeerMap::default(),
406 draft_positions: PeerMap::default(),
407 bot_storages: PeerMap::default(),
408 webview_bots: None,
409 webview_other: None,
410 wallet: BTreeMap::new(),
411 };
412 for entry in entries {
413 match entry {
414 MapEntry::FileKeys { kind, keys } => {
415 for (&at, &key) in key_slots(*kind).iter().zip(keys) {
416 state.keys[at] = key;
417 }
418 }
419 MapEntry::PeerKeys { kind, pairs } => {
420 let target = match *kind {
421 LSK_DRAFT => &mut state.drafts,
422 LSK_DRAFT_POSITION => &mut state.draft_positions,
423 LSK_BOT_STORAGES => &mut state.bot_storages,
424 _ => continue,
425 };
426 for &(file_key, peer) in pairs {
427 target.insert(peer_from_serialized(peer), file_key);
428 }
429 }
430 MapEntry::LegacyLocations { .. } => {}
431 MapEntry::SelfSerialized(blob) => state.self_serialized = blob.as_deref(),
432 MapEntry::WebviewTokens { bots, other } => {
433 state.webview_bots = bots.as_deref();
434 state.webview_other = other.as_deref();
435 }
436 MapEntry::WalletEngineStorages(items) => {
437 state.wallet = items
438 .iter()
439 .map(|(file_key, name)| (name.as_deref().unwrap_or_default(), *file_key))
440 .collect();
441 }
442 }
443 }
444 state
445 }
446
447 fn encode_keys(&self, writer: &mut QWriter, kind: u32) {
449 let slots = key_slots(kind);
450 if slots.iter().any(|&at| self.keys[at] != 0) {
451 writer.write_u32(kind);
452 for &at in slots {
453 writer.write_u64(self.keys[at]);
454 }
455 }
456 }
457
458 fn encode(&self) -> Result<Vec<u8>> {
459 let mut writer = QWriter::new();
460 if let Some(blob) = self.self_serialized.filter(|blob| !blob.is_empty()) {
461 writer.write_u32(LSK_SELF_SERIALIZED);
462 writer.write_bytearray(Some(blob))?;
463 }
464 self.encode_keys(&mut writer, 0x1E);
466 self.drafts.encode(&mut writer, LSK_DRAFT)?;
467 self.draft_positions
468 .encode(&mut writer, LSK_DRAFT_POSITION)?;
469 self.bot_storages.encode(&mut writer, LSK_BOT_STORAGES)?;
470 for kind in [
474 0x04, 0x11, 0x07, 0x10, 0x12, 0x0F, 0x09, 0x0A, 0x13, 0x16, 0x17, 0x18,
475 ] {
476 self.encode_keys(&mut writer, kind);
477 }
478 let non_empty = |token: Option<&[u8]>| token.is_some_and(|t| !t.is_empty());
479 if non_empty(self.webview_bots) || non_empty(self.webview_other) {
480 writer.write_u32(LSK_WEBVIEW_TOKENS);
481 writer.write_bytearray(self.webview_bots)?;
482 writer.write_bytearray(self.webview_other)?;
483 }
484 for kind in [0x1A, 0x1B, 0x1C] {
486 self.encode_keys(&mut writer, kind);
487 }
488 if !self.wallet.is_empty() {
489 let count = u32::try_from(self.wallet.len())
490 .map_err(|_| Error::QDataStreamFailed { context: None })?;
491 writer.write_u32(LSK_WALLET_ENGINE_STORAGES);
492 writer.write_u32(count);
493 for (&name, &file_key) in &self.wallet {
494 writer.write_u64(file_key);
495 writer.write_qstring(Some(name))?;
496 }
497 }
498 self.encode_keys(&mut writer, 0x14);
500 Ok(writer.into_bytes())
501 }
502}
503
504fn read_counted<'a, T>(
507 reader: &mut QReader<'a>,
508 mut item: impl FnMut(&mut QReader<'a>) -> Result<T>,
509) -> Result<Vec<T>> {
510 let count = reader.read_u32()?;
511 let mut items = Vec::new();
512 for _ in 0..count {
513 items.push(item(reader)?);
514 }
515 Ok(items)
516}
517
518fn owned(blob: Option<&[u8]>) -> Option<Vec<u8>> {
519 blob.map(<[u8]>::to_vec)
520}
521
522fn read_entry(reader: &mut QReader<'_>, kind: u32) -> Result<Option<MapEntry>> {
524 let entry = match kind {
525 LSK_DRAFT | LSK_DRAFT_POSITION | LSK_BOT_STORAGES => MapEntry::PeerKeys {
526 kind,
527 pairs: read_counted(reader, |r| Ok((r.read_u64()?, r.read_u64()?)))?,
528 },
529 LSK_LEGACY_IMAGES | LSK_LEGACY_STICKER_IMAGES | LSK_LEGACY_AUDIOS => {
530 MapEntry::LegacyLocations {
531 kind,
532 items: read_counted(reader, |r| {
533 Ok((r.read_u64()?, r.read_u64()?, r.read_u64()?, r.read_i32()?))
534 })?,
535 }
536 }
537 LSK_SELF_SERIALIZED => MapEntry::SelfSerialized(owned(reader.read_bytearray()?)),
538 LSK_WEBVIEW_TOKENS => MapEntry::WebviewTokens {
539 bots: owned(reader.read_bytearray()?),
540 other: owned(reader.read_bytearray()?),
541 },
542 LSK_WALLET_ENGINE_STORAGES => MapEntry::WalletEngineStorages(read_counted(reader, |r| {
543 Ok((r.read_u64()?, r.read_qstring()?))
544 })?),
545 _ => {
546 let Some(count) = file_key_count(kind) else {
547 return Ok(None);
548 };
549 let mut keys = Vec::with_capacity(count);
550 for _ in 0..count {
551 keys.push(reader.read_u64()?);
552 }
553 MapEntry::FileKeys { kind, keys }
554 }
555 };
556 Ok(Some(entry))
557}