mirror of
https://github.com/jkcoxson/idevice.git
synced 2026-03-02 14:36:16 +01:00
Initial afc support
This commit is contained in:
@@ -43,6 +43,7 @@ tun-rs = { version = "2.0.8", features = ["async_tokio"] }
|
|||||||
bytes = "1.10.1"
|
bytes = "1.10.1"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
afc = []
|
||||||
core_device_proxy = ["dep:serde_json", "dep:json", "dep:byteorder"]
|
core_device_proxy = ["dep:serde_json", "dep:json", "dep:byteorder"]
|
||||||
debug_proxy = []
|
debug_proxy = []
|
||||||
dvt = ["dep:byteorder", "dep:ns-keyed-archive"]
|
dvt = ["dep:byteorder", "dep:ns-keyed-archive"]
|
||||||
@@ -66,6 +67,7 @@ xpc = [
|
|||||||
"dep:json",
|
"dep:json",
|
||||||
]
|
]
|
||||||
full = [
|
full = [
|
||||||
|
"afc",
|
||||||
"core_device_proxy",
|
"core_device_proxy",
|
||||||
"debug_proxy",
|
"debug_proxy",
|
||||||
"dvt",
|
"dvt",
|
||||||
@@ -80,7 +82,7 @@ full = [
|
|||||||
"tunnel_tcp_stack",
|
"tunnel_tcp_stack",
|
||||||
"tss",
|
"tss",
|
||||||
"tunneld",
|
"tunneld",
|
||||||
"sbservices"
|
"sbservices",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Why: https://github.com/rust-lang/cargo/issues/1197
|
# Why: https://github.com/rust-lang/cargo/issues/1197
|
||||||
|
|||||||
107
idevice/src/afc/errors.rs
Normal file
107
idevice/src/afc/errors.rs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
// Jackson Coxson
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[repr(C)]
|
||||||
|
pub enum AfcError {
|
||||||
|
Success = 0,
|
||||||
|
UnknownError = 1,
|
||||||
|
OpHeaderInvalid = 2,
|
||||||
|
NoResources = 3,
|
||||||
|
ReadError = 4,
|
||||||
|
WriteError = 5,
|
||||||
|
UnknownPacketType = 6,
|
||||||
|
InvalidArg = 7,
|
||||||
|
ObjectNotFound = 8,
|
||||||
|
ObjectIsDir = 9,
|
||||||
|
PermDenied = 10,
|
||||||
|
ServiceNotConnected = 11,
|
||||||
|
OpTimeout = 12,
|
||||||
|
TooMuchData = 13,
|
||||||
|
EndOfData = 14,
|
||||||
|
OpNotSupported = 15,
|
||||||
|
ObjectExists = 16,
|
||||||
|
ObjectBusy = 17,
|
||||||
|
NoSpaceLeft = 18,
|
||||||
|
OpWouldBlock = 19,
|
||||||
|
IoError = 20,
|
||||||
|
OpInterrupted = 21,
|
||||||
|
OpInProgress = 22,
|
||||||
|
InternalError = 23,
|
||||||
|
MuxError = 30,
|
||||||
|
NoMem = 31,
|
||||||
|
NotEnoughData = 32,
|
||||||
|
DirNotEmpty = 33,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for AfcError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let description = match self {
|
||||||
|
AfcError::Success => "Success",
|
||||||
|
AfcError::UnknownError => "Unknown error",
|
||||||
|
AfcError::OpHeaderInvalid => "Operation header invalid",
|
||||||
|
AfcError::NoResources => "No resources available",
|
||||||
|
AfcError::ReadError => "Read error",
|
||||||
|
AfcError::WriteError => "Write error",
|
||||||
|
AfcError::UnknownPacketType => "Unknown packet type",
|
||||||
|
AfcError::InvalidArg => "Invalid argument",
|
||||||
|
AfcError::ObjectNotFound => "Object not found",
|
||||||
|
AfcError::ObjectIsDir => "Object is a directory",
|
||||||
|
AfcError::PermDenied => "Permission denied",
|
||||||
|
AfcError::ServiceNotConnected => "Service not connected",
|
||||||
|
AfcError::OpTimeout => "Operation timed out",
|
||||||
|
AfcError::TooMuchData => "Too much data",
|
||||||
|
AfcError::EndOfData => "End of data",
|
||||||
|
AfcError::OpNotSupported => "Operation not supported",
|
||||||
|
AfcError::ObjectExists => "Object already exists",
|
||||||
|
AfcError::ObjectBusy => "Object is busy",
|
||||||
|
AfcError::NoSpaceLeft => "No space left",
|
||||||
|
AfcError::OpWouldBlock => "Operation would block",
|
||||||
|
AfcError::IoError => "I/O error",
|
||||||
|
AfcError::OpInterrupted => "Operation interrupted",
|
||||||
|
AfcError::OpInProgress => "Operation in progress",
|
||||||
|
AfcError::InternalError => "Internal error",
|
||||||
|
AfcError::MuxError => "Multiplexer error",
|
||||||
|
AfcError::NoMem => "Out of memory",
|
||||||
|
AfcError::NotEnoughData => "Not enough data",
|
||||||
|
AfcError::DirNotEmpty => "Directory not empty",
|
||||||
|
};
|
||||||
|
write!(f, "{}", description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u64> for AfcError {
|
||||||
|
fn from(value: u64) -> Self {
|
||||||
|
match value {
|
||||||
|
0 => Self::Success,
|
||||||
|
1 => Self::UnknownError,
|
||||||
|
2 => Self::OpHeaderInvalid,
|
||||||
|
3 => Self::NoResources,
|
||||||
|
4 => Self::ReadError,
|
||||||
|
5 => Self::WriteError,
|
||||||
|
6 => Self::UnknownPacketType,
|
||||||
|
7 => Self::InvalidArg,
|
||||||
|
8 => Self::ObjectNotFound,
|
||||||
|
9 => Self::ObjectIsDir,
|
||||||
|
10 => Self::PermDenied,
|
||||||
|
11 => Self::ServiceNotConnected,
|
||||||
|
12 => Self::OpTimeout,
|
||||||
|
13 => Self::TooMuchData,
|
||||||
|
14 => Self::EndOfData,
|
||||||
|
15 => Self::OpNotSupported,
|
||||||
|
16 => Self::ObjectExists,
|
||||||
|
17 => Self::ObjectBusy,
|
||||||
|
18 => Self::NoSpaceLeft,
|
||||||
|
19 => Self::OpWouldBlock,
|
||||||
|
20 => Self::IoError,
|
||||||
|
21 => Self::OpInterrupted,
|
||||||
|
22 => Self::OpInProgress,
|
||||||
|
23 => Self::InternalError,
|
||||||
|
30 => Self::MuxError,
|
||||||
|
31 => Self::NoMem,
|
||||||
|
32 => Self::NotEnoughData,
|
||||||
|
33 => Self::DirNotEmpty,
|
||||||
|
_ => Self::UnknownError, // fallback for unknown codes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
107
idevice/src/afc/mod.rs
Normal file
107
idevice/src/afc/mod.rs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
// Jackson Coxson
|
||||||
|
|
||||||
|
use errors::AfcError;
|
||||||
|
use opcode::AfcOpcode;
|
||||||
|
use packet::{AfcPacket, AfcPacketHeader};
|
||||||
|
|
||||||
|
use crate::{lockdownd::LockdowndClient, Idevice, IdeviceError, IdeviceService};
|
||||||
|
|
||||||
|
pub mod errors;
|
||||||
|
pub mod opcode;
|
||||||
|
pub mod packet;
|
||||||
|
|
||||||
|
pub const MAGIC: u64 = 0x4141504c36414643;
|
||||||
|
|
||||||
|
pub struct AfcClient {
|
||||||
|
pub idevice: Idevice,
|
||||||
|
package_number: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IdeviceService for AfcClient {
|
||||||
|
fn service_name() -> &'static str {
|
||||||
|
"com.apple.afc"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn connect(
|
||||||
|
provider: &dyn crate::provider::IdeviceProvider,
|
||||||
|
) -> Result<Self, IdeviceError> {
|
||||||
|
let mut lockdown = LockdowndClient::connect(provider).await?;
|
||||||
|
lockdown
|
||||||
|
.start_session(&provider.get_pairing_file().await?)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let (port, ssl) = lockdown.start_service(Self::service_name()).await?;
|
||||||
|
|
||||||
|
let mut idevice = provider.connect(port).await?;
|
||||||
|
if ssl {
|
||||||
|
idevice
|
||||||
|
.start_session(&provider.get_pairing_file().await?)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
idevice,
|
||||||
|
package_number: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AfcClient {
|
||||||
|
pub fn new(idevice: Idevice) -> Self {
|
||||||
|
Self {
|
||||||
|
idevice,
|
||||||
|
package_number: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn list(&mut self, path: impl Into<String>) -> Result<Vec<String>, 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::ReadDir,
|
||||||
|
};
|
||||||
|
self.package_number += 1;
|
||||||
|
|
||||||
|
let packet = AfcPacket {
|
||||||
|
header,
|
||||||
|
header_payload,
|
||||||
|
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();
|
||||||
|
Ok(strings)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn read(&mut self) -> Result<AfcPacket, IdeviceError> {
|
||||||
|
let res = AfcPacket::read(&mut self.idevice).await?;
|
||||||
|
if res.header.operation == AfcOpcode::Status {
|
||||||
|
if res.header_payload.len() < 8 {
|
||||||
|
log::error!("AFC returned error opcode, but not a code");
|
||||||
|
return Err(IdeviceError::UnexpectedResponse);
|
||||||
|
}
|
||||||
|
let code = u64::from_le_bytes(res.header_payload[..8].try_into().unwrap());
|
||||||
|
return Err(IdeviceError::Afc(AfcError::from(code)));
|
||||||
|
}
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send(&mut self, packet: AfcPacket) -> Result<(), IdeviceError> {
|
||||||
|
let packet = packet.serialize();
|
||||||
|
self.idevice.send_raw(&packet).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
83
idevice/src/afc/opcode.rs
Normal file
83
idevice/src/afc/opcode.rs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
// Jackson Coxson
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
#[repr(u64)]
|
||||||
|
pub enum AfcOpcode {
|
||||||
|
Status = 0x00000001,
|
||||||
|
Data = 0x00000002, // Data
|
||||||
|
ReadDir = 0x00000003, // ReadDir
|
||||||
|
ReadFile = 0x00000004, // ReadFile
|
||||||
|
WriteFile = 0x00000005, // WriteFile
|
||||||
|
WritePart = 0x00000006, // WritePart
|
||||||
|
Truncate = 0x00000007, // TruncateFile
|
||||||
|
RemovePath = 0x00000008, // RemovePath
|
||||||
|
MakeDir = 0x00000009, // MakeDir
|
||||||
|
GetFileInfo = 0x0000000a, // GetFileInfo
|
||||||
|
GetDevInfo = 0x0000000b, // GetDeviceInfo
|
||||||
|
WriteFileAtom = 0x0000000c, // WriteFileAtomic (tmp file+rename)
|
||||||
|
FileOpen = 0x0000000d, // FileRefOpen
|
||||||
|
FileOpenRes = 0x0000000e, // FileRefOpenResult
|
||||||
|
Read = 0x0000000f, // FileRefRead
|
||||||
|
Write = 0x00000010, // FileRefWrite
|
||||||
|
FileSeek = 0x00000011, // FileRefSeek
|
||||||
|
FileTell = 0x00000012, // FileRefTell
|
||||||
|
FileTellRes = 0x00000013, // FileRefTellResult
|
||||||
|
FileClose = 0x00000014, // FileRefClose
|
||||||
|
FileSetSize = 0x00000015, // FileRefSetFileSize (ftruncate)
|
||||||
|
GetConInfo = 0x00000016, // GetConnectionInfo
|
||||||
|
SetConOptions = 0x00000017, // SetConnectionOptions
|
||||||
|
RenamePath = 0x00000018, // RenamePath
|
||||||
|
SetFsBs = 0x00000019, // SetFSBlockSize (0x800000)
|
||||||
|
SetSocketBs = 0x0000001A, // SetSocketBlockSize (0x800000)
|
||||||
|
FileLock = 0x0000001B, // FileRefLock
|
||||||
|
MakeLink = 0x0000001C, // MakeLink
|
||||||
|
SetFileTime = 0x0000001E, // Set st_mtime
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum AfcFopenMode {
|
||||||
|
RdOnly = 0x00000001, // r O_RDONLY
|
||||||
|
Rw = 0x00000002, // r+ O_RDWR | O_CREAT
|
||||||
|
WrOnly = 0x00000003, // w O_WRONLY | O_CREAT | O_TRUNC
|
||||||
|
Wr = 0x00000004, // w+ O_RDWR | O_CREAT | O_TRUNC
|
||||||
|
Append = 0x00000005, // a O_WRONLY | O_APPEND | O_CREAT
|
||||||
|
RdAppend = 0x00000006, // a+ O_RDWR | O_APPEND | O_CREAT
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u64> for AfcOpcode {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(value: u64) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
0x00000001 => Ok(Self::Status),
|
||||||
|
0x00000002 => Ok(Self::Data),
|
||||||
|
0x00000003 => Ok(Self::ReadDir),
|
||||||
|
0x00000004 => Ok(Self::ReadFile),
|
||||||
|
0x00000005 => Ok(Self::WriteFile),
|
||||||
|
0x00000006 => Ok(Self::WritePart),
|
||||||
|
0x00000007 => Ok(Self::Truncate),
|
||||||
|
0x00000008 => Ok(Self::RemovePath),
|
||||||
|
0x00000009 => Ok(Self::MakeDir),
|
||||||
|
0x0000000a => Ok(Self::GetFileInfo),
|
||||||
|
0x0000000b => Ok(Self::GetDevInfo),
|
||||||
|
0x0000000c => Ok(Self::WriteFileAtom),
|
||||||
|
0x0000000d => Ok(Self::FileOpen),
|
||||||
|
0x0000000e => Ok(Self::FileOpenRes),
|
||||||
|
0x0000000f => Ok(Self::Read),
|
||||||
|
0x00000010 => Ok(Self::Write),
|
||||||
|
0x00000011 => Ok(Self::FileSeek),
|
||||||
|
0x00000012 => Ok(Self::FileTell),
|
||||||
|
0x00000013 => Ok(Self::FileTellRes),
|
||||||
|
0x00000014 => Ok(Self::FileClose),
|
||||||
|
0x00000015 => Ok(Self::FileSetSize),
|
||||||
|
0x00000016 => Ok(Self::GetConInfo),
|
||||||
|
0x00000017 => Ok(Self::SetConOptions),
|
||||||
|
0x00000018 => Ok(Self::RenamePath),
|
||||||
|
0x00000019 => Ok(Self::SetFsBs),
|
||||||
|
0x0000001A => Ok(Self::SetSocketBs),
|
||||||
|
0x0000001B => Ok(Self::FileLock),
|
||||||
|
0x0000001C => Ok(Self::MakeLink),
|
||||||
|
0x0000001E => Ok(Self::SetFileTime),
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
98
idevice/src/afc/packet.rs
Normal file
98
idevice/src/afc/packet.rs
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
// Jackson Coxson
|
||||||
|
|
||||||
|
use log::debug;
|
||||||
|
|
||||||
|
use crate::{Idevice, IdeviceError};
|
||||||
|
|
||||||
|
use super::opcode::AfcOpcode;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct AfcPacketHeader {
|
||||||
|
pub magic: u64,
|
||||||
|
pub entire_len: u64,
|
||||||
|
pub header_payload_len: u64,
|
||||||
|
pub packet_num: u64,
|
||||||
|
pub operation: AfcOpcode,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct AfcPacket {
|
||||||
|
pub header: AfcPacketHeader,
|
||||||
|
pub header_payload: Vec<u8>,
|
||||||
|
pub payload: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AfcPacketHeader {
|
||||||
|
pub const LEN: u64 = 40;
|
||||||
|
|
||||||
|
pub fn serialize(&self) -> Vec<u8> {
|
||||||
|
let mut res = Vec::with_capacity(Self::LEN as usize);
|
||||||
|
|
||||||
|
res.extend_from_slice(&self.magic.to_le_bytes());
|
||||||
|
res.extend_from_slice(&self.entire_len.to_le_bytes());
|
||||||
|
res.extend_from_slice(&self.header_payload_len.to_le_bytes());
|
||||||
|
res.extend_from_slice(&self.packet_num.to_le_bytes());
|
||||||
|
res.extend_from_slice(&(self.operation.clone() as u64).to_le_bytes());
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn read(reader: &mut Idevice) -> Result<Self, IdeviceError> {
|
||||||
|
let header_bytes = reader.read_raw(Self::LEN as usize).await?;
|
||||||
|
let mut chunks = header_bytes.chunks_exact(8);
|
||||||
|
let res = Self {
|
||||||
|
magic: u64::from_le_bytes(chunks.next().unwrap().try_into().unwrap()),
|
||||||
|
entire_len: u64::from_le_bytes(chunks.next().unwrap().try_into().unwrap()),
|
||||||
|
header_payload_len: u64::from_le_bytes(chunks.next().unwrap().try_into().unwrap()),
|
||||||
|
packet_num: u64::from_le_bytes(chunks.next().unwrap().try_into().unwrap()),
|
||||||
|
operation: match AfcOpcode::try_from(u64::from_le_bytes(
|
||||||
|
chunks.next().unwrap().try_into().unwrap(),
|
||||||
|
)) {
|
||||||
|
Ok(o) => o,
|
||||||
|
Err(_) => {
|
||||||
|
return Err(IdeviceError::UnknownAfcOpcode);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if res.magic != super::MAGIC {
|
||||||
|
return Err(IdeviceError::InvalidAfcMagic);
|
||||||
|
}
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AfcPacket {
|
||||||
|
pub fn serialize(&self) -> Vec<u8> {
|
||||||
|
let mut res = Vec::new();
|
||||||
|
|
||||||
|
res.extend_from_slice(&self.header.serialize());
|
||||||
|
res.extend_from_slice(&self.header_payload);
|
||||||
|
res.extend_from_slice(&self.payload);
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn read(reader: &mut Idevice) -> Result<Self, IdeviceError> {
|
||||||
|
let header = AfcPacketHeader::read(reader).await?;
|
||||||
|
debug!("afc header: {header:?}");
|
||||||
|
let header_payload = reader
|
||||||
|
.read_raw((header.header_payload_len - AfcPacketHeader::LEN) as usize)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let payload = if header.header_payload_len == header.entire_len {
|
||||||
|
Vec::new() // no payload
|
||||||
|
} else {
|
||||||
|
reader
|
||||||
|
.read_raw((header.entire_len - header.header_payload_len) as usize)
|
||||||
|
.await?
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = Self {
|
||||||
|
header,
|
||||||
|
header_payload,
|
||||||
|
payload,
|
||||||
|
};
|
||||||
|
debug!("Recv afc: {res:?}");
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
// Jackson Coxson
|
// Jackson Coxson
|
||||||
|
|
||||||
|
#[cfg(feature = "afc")]
|
||||||
|
pub mod afc;
|
||||||
#[cfg(feature = "core_device_proxy")]
|
#[cfg(feature = "core_device_proxy")]
|
||||||
pub mod core_device_proxy;
|
pub mod core_device_proxy;
|
||||||
#[cfg(feature = "debug_proxy")]
|
#[cfg(feature = "debug_proxy")]
|
||||||
@@ -298,6 +300,18 @@ pub enum IdeviceError {
|
|||||||
#[error("misagent operation failed")]
|
#[error("misagent operation failed")]
|
||||||
MisagentFailure,
|
MisagentFailure,
|
||||||
|
|
||||||
|
#[cfg(feature = "afc")]
|
||||||
|
#[error("afc error")]
|
||||||
|
Afc(#[from] afc::errors::AfcError),
|
||||||
|
|
||||||
|
#[cfg(feature = "afc")]
|
||||||
|
#[error("unknown afc opcode")]
|
||||||
|
UnknownAfcOpcode,
|
||||||
|
|
||||||
|
#[cfg(feature = "afc")]
|
||||||
|
#[error("invalid afc magic")]
|
||||||
|
InvalidAfcMagic,
|
||||||
|
|
||||||
#[cfg(any(feature = "tss", feature = "tunneld"))]
|
#[cfg(any(feature = "tss", feature = "tunneld"))]
|
||||||
#[error("http reqwest error")]
|
#[error("http reqwest error")]
|
||||||
Reqwest(#[from] reqwest::Error),
|
Reqwest(#[from] reqwest::Error),
|
||||||
|
|||||||
@@ -57,6 +57,11 @@ path = "src/misagent.rs"
|
|||||||
name = "location_simulation"
|
name = "location_simulation"
|
||||||
path = "src/location_simulation.rs"
|
path = "src/location_simulation.rs"
|
||||||
|
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "afc"
|
||||||
|
path = "src/afc.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
idevice = { path = "../idevice", features = ["full"] }
|
idevice = { path = "../idevice", features = ["full"] }
|
||||||
tokio = { version = "1.43", features = ["io-util", "macros", "time", "full"] }
|
tokio = { version = "1.43", features = ["io-util", "macros", "time", "full"] }
|
||||||
|
|||||||
81
tools/src/afc.rs
Normal file
81
tools/src/afc.rs
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
// Jackson Coxson
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use clap::{arg, value_parser, Arg, Command};
|
||||||
|
use idevice::{afc::AfcClient, misagent::MisagentClient, IdeviceService};
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
|
let matches = Command::new("afc")
|
||||||
|
.about("Start a tunnel")
|
||||||
|
.arg(
|
||||||
|
Arg::new("host")
|
||||||
|
.long("host")
|
||||||
|
.value_name("HOST")
|
||||||
|
.help("IP address of the device"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("pairing_file")
|
||||||
|
.long("pairing-file")
|
||||||
|
.value_name("PATH")
|
||||||
|
.help("Path to the pairing file"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("udid")
|
||||||
|
.value_name("UDID")
|
||||||
|
.help("UDID of the device (overrides host/pairing file)"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("about")
|
||||||
|
.long("about")
|
||||||
|
.help("Show about information")
|
||||||
|
.action(clap::ArgAction::SetTrue),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
Command::new("list")
|
||||||
|
.about("Lists the items in the directory")
|
||||||
|
.arg(Arg::new("path").required(true).index(1)),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
Command::new("remove")
|
||||||
|
.about("Remove a provisioning profile")
|
||||||
|
.arg(Arg::new("path").required(true).index(1)),
|
||||||
|
)
|
||||||
|
.get_matches();
|
||||||
|
|
||||||
|
if matches.get_flag("about") {
|
||||||
|
println!("afc");
|
||||||
|
println!("Copyright (c) 2025 Jackson Coxson");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let udid = matches.get_one::<String>("udid");
|
||||||
|
let host = matches.get_one::<String>("host");
|
||||||
|
let pairing_file = matches.get_one::<String>("pairing_file");
|
||||||
|
|
||||||
|
let provider = match common::get_provider(udid, host, pairing_file, "afc-jkcoxson").await {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("{e}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut afc_client = AfcClient::connect(&*provider)
|
||||||
|
.await
|
||||||
|
.expect("Unable to connect to misagent");
|
||||||
|
|
||||||
|
if let Some(matches) = matches.subcommand_matches("list") {
|
||||||
|
let path = matches.get_one::<String>("path").expect("No path passed");
|
||||||
|
let res = afc_client.list(path).await.expect("Failed to read dir");
|
||||||
|
println!("{path}\n{res:#?}");
|
||||||
|
} else if let Some(matches) = matches.subcommand_matches("remove") {
|
||||||
|
let path = matches.get_one::<String>("id").expect("No path passed");
|
||||||
|
} else {
|
||||||
|
eprintln!("Invalid usage, pass -h for help");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user