mirror of
https://github.com/jkcoxson/idevice.git
synced 2026-03-02 06:26:15 +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 }
|
||||
async-recursion = { version = "1.1", 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 }
|
||||
json = { version = "0.12", optional = true }
|
||||
|
||||
@@ -20,7 +20,7 @@ pub struct AfcClient {
|
||||
package_number: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FileInfo {
|
||||
pub size: usize,
|
||||
pub blocks: usize,
|
||||
@@ -31,6 +31,14 @@ pub struct FileInfo {
|
||||
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 {
|
||||
fn service_name() -> &'static str {
|
||||
"com.apple.afc"
|
||||
@@ -167,30 +175,30 @@ impl AfcClient {
|
||||
let size = kvs
|
||||
.remove("st_size")
|
||||
.and_then(|x| x.parse::<usize>().ok())
|
||||
.ok_or(IdeviceError::AfcMissingFileAttribute)?;
|
||||
.ok_or(IdeviceError::AfcMissingAttribute)?;
|
||||
let blocks = kvs
|
||||
.remove("st_blocks")
|
||||
.and_then(|x| x.parse::<usize>().ok())
|
||||
.ok_or(IdeviceError::AfcMissingFileAttribute)?;
|
||||
.ok_or(IdeviceError::AfcMissingAttribute)?;
|
||||
|
||||
let creation = kvs
|
||||
.remove("st_birthtime")
|
||||
.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 modified = kvs
|
||||
.remove("st_mtime")
|
||||
.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 st_nlink = kvs
|
||||
.remove("st_nlink")
|
||||
.ok_or(IdeviceError::AfcMissingFileAttribute)?;
|
||||
.ok_or(IdeviceError::AfcMissingAttribute)?;
|
||||
let st_ifmt = kvs
|
||||
.remove("st_ifmt")
|
||||
.ok_or(IdeviceError::AfcMissingFileAttribute)?;
|
||||
.ok_or(IdeviceError::AfcMissingAttribute)?;
|
||||
let st_link_target = kvs.remove("st_link_target");
|
||||
|
||||
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> {
|
||||
let res = AfcPacket::read(&mut self.idevice).await?;
|
||||
if res.header.operation == AfcOpcode::Status {
|
||||
|
||||
@@ -32,6 +32,7 @@ pub enum AfcOpcode {
|
||||
FileLock = 0x0000001B, // FileRefLock
|
||||
MakeLink = 0x0000001C, // MakeLink
|
||||
SetFileTime = 0x0000001E, // Set st_mtime
|
||||
RemovePathAndContents = 0x00000022,
|
||||
}
|
||||
|
||||
pub enum AfcFopenMode {
|
||||
|
||||
@@ -314,7 +314,7 @@ pub enum IdeviceError {
|
||||
|
||||
#[cfg(feature = "afc")]
|
||||
#[error("missing file attribute")]
|
||||
AfcMissingFileAttribute,
|
||||
AfcMissingAttribute,
|
||||
|
||||
#[cfg(any(feature = "tss", feature = "tunneld"))]
|
||||
#[error("http reqwest error")]
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// Jackson Coxson
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::{arg, value_parser, Arg, Command};
|
||||
use idevice::{afc::AfcClient, misagent::MisagentClient, IdeviceService};
|
||||
use clap::{Arg, Command};
|
||||
use idevice::{afc::AfcClient, IdeviceService};
|
||||
|
||||
mod common;
|
||||
|
||||
@@ -51,11 +49,17 @@ async fn main() {
|
||||
.about("Remove a provisioning profile")
|
||||
.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(
|
||||
Command::new("info")
|
||||
.about("Get info about a file")
|
||||
.arg(Arg::new("path").required(true).index(1)),
|
||||
)
|
||||
.subcommand(Command::new("device_info").about("Get info about the device"))
|
||||
.get_matches();
|
||||
|
||||
if matches.get_flag("about") {
|
||||
@@ -87,7 +91,11 @@ async fn main() {
|
||||
let path = matches.get_one::<String>("path").expect("No path passed");
|
||||
afc_client.mk_dir(path).await.expect("Failed to mkdir");
|
||||
} 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") {
|
||||
let path = matches.get_one::<String>("path").expect("No path passed");
|
||||
let res = afc_client
|
||||
@@ -95,6 +103,12 @@ async fn main() {
|
||||
.await
|
||||
.expect("Failed to get file info");
|
||||
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 {
|
||||
eprintln!("Invalid usage, pass -h for help");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user