mirror of
https://github.com/jkcoxson/idevice.git
synced 2026-03-02 14:36:16 +01:00
afc get device info
This commit is contained in:
@@ -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 }
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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")]
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user