afc get device info

This commit is contained in:
Jackson Coxson
2025-04-05 12:07:55 -06:00
parent 65df561ea1
commit 12f2a22b1e
5 changed files with 150 additions and 14 deletions

View File

@@ -26,7 +26,7 @@ indexmap = { version = "2.7", features = ["serde"], optional = true }
uuid = { version = "1.12", features = ["serde", "v4"], optional = true } uuid = { version = "1.12", features = ["serde", "v4"], optional = true }
async-recursion = { version = "1.1", optional = true } async-recursion = { version = "1.1", optional = true }
base64 = { version = "0.22", optional = true } base64 = { version = "0.22", optional = true }
chrono = { version = "0.4.40", optional = true, default_features = false } chrono = { version = "0.4.40", optional = true, default-features = false }
serde_json = { version = "1", optional = true } serde_json = { version = "1", optional = true }
json = { version = "0.12", optional = true } json = { version = "0.12", optional = true }

View File

@@ -20,7 +20,7 @@ pub struct AfcClient {
package_number: u64, package_number: u64,
} }
#[derive(Debug)] #[derive(Clone, Debug)]
pub struct FileInfo { pub struct FileInfo {
pub size: usize, pub size: usize,
pub blocks: usize, pub blocks: usize,
@@ -31,6 +31,14 @@ pub struct FileInfo {
pub st_link_target: Option<String>, pub st_link_target: Option<String>,
} }
#[derive(Clone, Debug)]
pub struct DeviceInfo {
model: String,
total_bytes: usize,
free_bytes: usize,
block_size: usize,
}
impl IdeviceService for AfcClient { impl IdeviceService for AfcClient {
fn service_name() -> &'static str { fn service_name() -> &'static str {
"com.apple.afc" "com.apple.afc"
@@ -167,30 +175,30 @@ impl AfcClient {
let size = kvs let size = kvs
.remove("st_size") .remove("st_size")
.and_then(|x| x.parse::<usize>().ok()) .and_then(|x| x.parse::<usize>().ok())
.ok_or(IdeviceError::AfcMissingFileAttribute)?; .ok_or(IdeviceError::AfcMissingAttribute)?;
let blocks = kvs let blocks = kvs
.remove("st_blocks") .remove("st_blocks")
.and_then(|x| x.parse::<usize>().ok()) .and_then(|x| x.parse::<usize>().ok())
.ok_or(IdeviceError::AfcMissingFileAttribute)?; .ok_or(IdeviceError::AfcMissingAttribute)?;
let creation = kvs let creation = kvs
.remove("st_birthtime") .remove("st_birthtime")
.and_then(|x| x.parse::<i64>().ok()) .and_then(|x| x.parse::<i64>().ok())
.ok_or(IdeviceError::AfcMissingFileAttribute)?; .ok_or(IdeviceError::AfcMissingAttribute)?;
let creation = chrono::DateTime::from_timestamp_nanos(creation).naive_local(); let creation = chrono::DateTime::from_timestamp_nanos(creation).naive_local();
let modified = kvs let modified = kvs
.remove("st_mtime") .remove("st_mtime")
.and_then(|x| x.parse::<i64>().ok()) .and_then(|x| x.parse::<i64>().ok())
.ok_or(IdeviceError::AfcMissingFileAttribute)?; .ok_or(IdeviceError::AfcMissingAttribute)?;
let modified = chrono::DateTime::from_timestamp_nanos(modified).naive_local(); let modified = chrono::DateTime::from_timestamp_nanos(modified).naive_local();
let st_nlink = kvs let st_nlink = kvs
.remove("st_nlink") .remove("st_nlink")
.ok_or(IdeviceError::AfcMissingFileAttribute)?; .ok_or(IdeviceError::AfcMissingAttribute)?;
let st_ifmt = kvs let st_ifmt = kvs
.remove("st_ifmt") .remove("st_ifmt")
.ok_or(IdeviceError::AfcMissingFileAttribute)?; .ok_or(IdeviceError::AfcMissingAttribute)?;
let st_link_target = kvs.remove("st_link_target"); let st_link_target = kvs.remove("st_link_target");
if !kvs.is_empty() { if !kvs.is_empty() {
@@ -208,6 +216,119 @@ impl AfcClient {
}) })
} }
pub async fn get_device_info(&mut self) -> Result<DeviceInfo, IdeviceError> {
let header_len = AfcPacketHeader::LEN;
let header = AfcPacketHeader {
magic: MAGIC,
entire_len: header_len, // it's the same since the payload is empty for this
header_payload_len: header_len,
packet_num: self.package_number,
operation: AfcOpcode::GetDevInfo,
};
self.package_number += 1;
let packet = AfcPacket {
header,
header_payload: Vec::new(),
payload: Vec::new(),
};
self.send(packet).await?;
let res = self.read().await?;
let strings: Vec<String> = res
.payload
.split(|b| *b == 0)
.filter(|s| !s.is_empty())
.map(|s| String::from_utf8_lossy(s).into_owned())
.collect();
let mut kvs: HashMap<String, String> = strings
.chunks_exact(2)
.map(|chunk| (chunk[0].clone(), chunk[1].clone()))
.collect();
let model = kvs
.remove("Model")
.ok_or(IdeviceError::AfcMissingAttribute)?;
let total_bytes = kvs
.remove("FSTotalBytes")
.and_then(|x| x.parse::<usize>().ok())
.ok_or(IdeviceError::AfcMissingAttribute)?;
let free_bytes = kvs
.remove("FSFreeBytes")
.and_then(|x| x.parse::<usize>().ok())
.ok_or(IdeviceError::AfcMissingAttribute)?;
let block_size = kvs
.remove("FSBlockSize")
.and_then(|x| x.parse::<usize>().ok())
.ok_or(IdeviceError::AfcMissingAttribute)?;
if !kvs.is_empty() {
warn!("Device info kvs not empty: {kvs:?}");
}
Ok(DeviceInfo {
model,
total_bytes,
free_bytes,
block_size,
})
}
pub async fn remove(&mut self, path: impl Into<String>) -> Result<(), IdeviceError> {
let path = path.into();
let header_payload = path.as_bytes().to_vec();
let header_len = header_payload.len() as u64 + AfcPacketHeader::LEN;
let header = AfcPacketHeader {
magic: MAGIC,
entire_len: header_len, // it's the same since the payload is empty for this
header_payload_len: header_len,
packet_num: self.package_number,
operation: AfcOpcode::RemovePath,
};
self.package_number += 1;
let packet = AfcPacket {
header,
header_payload,
payload: Vec::new(),
};
self.send(packet).await?;
self.read().await?; // read a response to check for errors
Ok(())
}
pub async fn remove_all(&mut self, path: impl Into<String>) -> Result<(), IdeviceError> {
let path = path.into();
let header_payload = path.as_bytes().to_vec();
let header_len = header_payload.len() as u64 + AfcPacketHeader::LEN;
let header = AfcPacketHeader {
magic: MAGIC,
entire_len: header_len, // it's the same since the payload is empty for this
header_payload_len: header_len,
packet_num: self.package_number,
operation: AfcOpcode::RemovePathAndContents,
};
self.package_number += 1;
let packet = AfcPacket {
header,
header_payload,
payload: Vec::new(),
};
self.send(packet).await?;
self.read().await?; // read a response to check for errors
Ok(())
}
pub async fn read(&mut self) -> Result<AfcPacket, IdeviceError> { pub async fn read(&mut self) -> Result<AfcPacket, IdeviceError> {
let res = AfcPacket::read(&mut self.idevice).await?; let res = AfcPacket::read(&mut self.idevice).await?;
if res.header.operation == AfcOpcode::Status { if res.header.operation == AfcOpcode::Status {

View File

@@ -32,6 +32,7 @@ pub enum AfcOpcode {
FileLock = 0x0000001B, // FileRefLock FileLock = 0x0000001B, // FileRefLock
MakeLink = 0x0000001C, // MakeLink MakeLink = 0x0000001C, // MakeLink
SetFileTime = 0x0000001E, // Set st_mtime SetFileTime = 0x0000001E, // Set st_mtime
RemovePathAndContents = 0x00000022,
} }
pub enum AfcFopenMode { pub enum AfcFopenMode {

View File

@@ -314,7 +314,7 @@ pub enum IdeviceError {
#[cfg(feature = "afc")] #[cfg(feature = "afc")]
#[error("missing file attribute")] #[error("missing file attribute")]
AfcMissingFileAttribute, AfcMissingAttribute,
#[cfg(any(feature = "tss", feature = "tunneld"))] #[cfg(any(feature = "tss", feature = "tunneld"))]
#[error("http reqwest error")] #[error("http reqwest error")]

View File

@@ -1,9 +1,7 @@
// Jackson Coxson // Jackson Coxson
use std::path::PathBuf; use clap::{Arg, Command};
use idevice::{afc::AfcClient, IdeviceService};
use clap::{arg, value_parser, Arg, Command};
use idevice::{afc::AfcClient, misagent::MisagentClient, IdeviceService};
mod common; mod common;
@@ -51,11 +49,17 @@ async fn main() {
.about("Remove a provisioning profile") .about("Remove a provisioning profile")
.arg(Arg::new("path").required(true).index(1)), .arg(Arg::new("path").required(true).index(1)),
) )
.subcommand(
Command::new("remove_all")
.about("Remove a provisioning profile")
.arg(Arg::new("path").required(true).index(1)),
)
.subcommand( .subcommand(
Command::new("info") Command::new("info")
.about("Get info about a file") .about("Get info about a file")
.arg(Arg::new("path").required(true).index(1)), .arg(Arg::new("path").required(true).index(1)),
) )
.subcommand(Command::new("device_info").about("Get info about the device"))
.get_matches(); .get_matches();
if matches.get_flag("about") { if matches.get_flag("about") {
@@ -87,7 +91,11 @@ async fn main() {
let path = matches.get_one::<String>("path").expect("No path passed"); let path = matches.get_one::<String>("path").expect("No path passed");
afc_client.mk_dir(path).await.expect("Failed to mkdir"); afc_client.mk_dir(path).await.expect("Failed to mkdir");
} else if let Some(matches) = matches.subcommand_matches("remove") { } else if let Some(matches) = matches.subcommand_matches("remove") {
let path = matches.get_one::<String>("id").expect("No path passed"); let path = matches.get_one::<String>("path").expect("No path passed");
afc_client.remove(path).await.expect("Failed to remove");
} else if let Some(matches) = matches.subcommand_matches("remove_all") {
let path = matches.get_one::<String>("path").expect("No path passed");
afc_client.remove_all(path).await.expect("Failed to remove");
} else if let Some(matches) = matches.subcommand_matches("info") { } else if let Some(matches) = matches.subcommand_matches("info") {
let path = matches.get_one::<String>("path").expect("No path passed"); let path = matches.get_one::<String>("path").expect("No path passed");
let res = afc_client let res = afc_client
@@ -95,6 +103,12 @@ async fn main() {
.await .await
.expect("Failed to get file info"); .expect("Failed to get file info");
println!("{res:#?}"); println!("{res:#?}");
} else if matches.subcommand_matches("device_info").is_some() {
let res = afc_client
.get_device_info()
.await
.expect("Failed to get file info");
println!("{res:#?}");
} else { } else {
eprintln!("Invalid usage, pass -h for help"); eprintln!("Invalid usage, pass -h for help");
} }