use std::{ops::Deref, path::PathBuf};
use plist::Value;
use crate::{
backup::util::plist::{as_dictionary, get_key_as_data, get_key_as_int, get_key_as_uint},
error::{BackupError, Result},
};
#[derive(Debug, Clone)]
pub struct FileKeyPair {
pub protection_class_id: u32,
pub file_key: WrappedKey,
}
impl FileKeyPair {
pub(crate) fn new(key: &[u8]) -> Result<Self> {
let parts = key.split_at(4);
Ok(FileKeyPair {
protection_class_id: u32::from_le_bytes(
parts.0.try_into().map_err(BackupError::ConversionFailed)?,
),
file_key: WrappedKey(parts.1.to_vec()),
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WrappedKey(Vec<u8>);
impl AsRef<[u8]> for WrappedKey {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl From<Vec<u8>> for WrappedKey {
fn from(v: Vec<u8>) -> WrappedKey {
WrappedKey(v)
}
}
impl Deref for WrappedKey {
type Target = Vec<u8>;
fn deref(&self) -> &Vec<u8> {
&self.0
}
}
#[derive(Debug, Clone)]
pub struct MBFile {
pub last_modified: u64,
pub flags: u64,
pub group_id: i64,
pub last_status_change: u64,
pub birth: u64,
pub size: u64,
pub mode: u64,
pub user_id: Option<u64>,
pub inode_number: u64,
pub protection_class: u32,
pub encryption_key: Option<FileKeyPair>,
}
impl MBFile {
pub(crate) fn from_plist(plist_data: &Value) -> Result<MBFile> {
let dict = as_dictionary(plist_data)?;
let root_uid = dict
.get("$top")
.and_then(Value::as_dictionary)
.and_then(|d| d.get("root"))
.and_then(Value::as_uid)
.map(|u| u.get() as usize)
.ok_or_else(|| BackupError::MissingPlistKey("Missing root UID".into()))?;
let objects = dict
.get("$objects")
.and_then(Value::as_array)
.ok_or_else(|| BackupError::MissingPlistKey("Missing $objects array".into()))?;
let top_dict = as_dictionary(objects.get(root_uid).ok_or_else(|| {
BackupError::PlistParseError("Could not resolve MBFile Dictionary".into())
})?)?;
let encryption_key = if let Some(uid_val) =
top_dict.get("EncryptionKey").and_then(Value::as_uid)
{
let idx = uid_val.get() as usize;
let data_dict = objects
.get(idx)
.and_then(Value::as_dictionary)
.ok_or_else(|| {
BackupError::PlistParseError("EncryptionKey object is not a dictionary".into())
})?;
let data = get_key_as_data(data_dict, "NS.data")?;
Some(FileKeyPair::new(&data)?)
} else {
None
};
Ok(MBFile {
last_modified: get_key_as_uint(top_dict, "LastModified")?,
flags: get_key_as_uint(top_dict, "Flags")?,
group_id: get_key_as_int(top_dict, "GroupID")?,
last_status_change: get_key_as_uint(top_dict, "LastStatusChange")?,
birth: get_key_as_uint(top_dict, "Birth")?,
size: get_key_as_uint(top_dict, "Size")?,
mode: get_key_as_uint(top_dict, "Mode")?,
user_id: top_dict.get("UserID").and_then(Value::as_unsigned_integer),
inode_number: get_key_as_uint(top_dict, "InodeNumber")?,
protection_class: get_key_as_uint(top_dict, "ProtectionClass")? as u32,
encryption_key,
})
}
}
#[derive(Debug, Clone)]
pub struct BackupFileEntry {
pub file_id: String,
pub domain: String,
pub relative_path: String,
pub flags: u32,
pub metadata: MBFile,
}
impl BackupFileEntry {
#[must_use]
pub fn source(&self) -> PathBuf {
PathBuf::from(&self.file_id[0..2]).join(&self.file_id)
}
}