use std::path::Path;
use crate::crypto::{AuthKey, decrypt_local};
use crate::qdatastream::QDataStream;
use crate::{Error, Result};
use super::file_io::read_file;
#[derive(Debug)]
pub struct MtpData {
pub dc_id: i32,
pub user_id: i64,
pub auth_key: [u8; 256],
}
const K_WIDE_IDS_TAG: i64 = !0i64;
pub fn read_mtp_data(
base_path: &Path,
index: i32,
local_key: &AuthKey,
key_file: &str,
) -> Result<MtpData> {
let data_name = compose_data_string(key_file, index);
let data_name_key = compute_data_name_key(&data_name);
let file_name = to_file_part(data_name_key);
tracing::debug!("Looking for MTP data in file: {}", file_name);
let file = read_file(&file_name, base_path)?;
let mut stream = QDataStream::new(&file.data);
let encrypted = stream.read_qbytearray()?;
let decrypted = decrypt_local(&encrypted, local_key)?;
parse_mtp_authorization(&decrypted)
}
fn compose_data_string(key_file: &str, index: i32) -> String {
let base = key_file.replace('#', "");
if index > 0 { format!("{}#{}", base, index + 1) } else { base }
}
fn compute_data_name_key(data_name: &str) -> u64 {
use md5::{Digest, Md5};
let mut hasher = Md5::new();
hasher.update(data_name.as_bytes());
let result: [u8; 16] = hasher.finalize().into();
u64::from_le_bytes([
result[0], result[1], result[2], result[3], result[4], result[5], result[6], result[7],
])
}
fn to_file_part(val: u64) -> String {
let mut result = String::with_capacity(16);
let mut v = val;
for _ in 0..16 {
let nibble = (v & 0x0F) as u8;
let c =
if nibble < 0x0A { (b'0' + nibble) as char } else { (b'A' + (nibble - 0x0A)) as char };
result.push(c);
v >>= 4;
}
result
}
fn parse_mtp_authorization(data: &[u8]) -> Result<MtpData> {
let mut stream = QDataStream::new(data);
let block_id = stream.read_i32()?;
if block_id != 0x4B {
return Err(Error::invalid_format(format!(
"expected MtpAuthorization block (0x4B), got 0x{block_id:02X}"
)));
}
let serialized = stream.read_qbytearray()?;
let mut auth_stream = QDataStream::new(&serialized);
let first_int = auth_stream.read_i32()?;
let second_int = auth_stream.read_i32()?;
let combined = (i64::from(first_int) << 32) | i64::from(second_int as u32);
let (user_id, main_dc_id) = if combined == K_WIDE_IDS_TAG {
let uid = auth_stream.read_i64()?;
let dc = auth_stream.read_i32()?;
(uid, dc)
} else {
(i64::from(first_int), second_int)
};
tracing::debug!("MTP auth: user_id={}, main_dc_id={}", user_id, main_dc_id);
let keys_count = auth_stream.read_i32()?;
if !(0..=10).contains(&keys_count) {
return Err(Error::invalid_format(format!("invalid keys count: {keys_count}")));
}
let mut auth_key: Option<[u8; 256]> = None;
for _ in 0..keys_count {
let dc_id = auth_stream.read_i32()?;
let key_bytes = auth_stream.read_raw(256)?;
tracing::debug!("Found key for DC {}", dc_id);
if dc_id == main_dc_id {
let mut key = [0u8; 256];
key.copy_from_slice(&key_bytes);
auth_key = Some(key);
}
}
let auth_key = auth_key.ok_or_else(|| {
Error::auth_key_failed(format!("no auth key found for main DC {main_dc_id}"))
})?;
Ok(MtpData { dc_id: main_dc_id, user_id, auth_key })
}