use std::{
collections::HashMap,
convert::TryFrom,
ffi::{CStr, CString},
os::raw::c_char,
};
use log::warn;
use crate::{
bindings as unsafe_bindings, error::AfcError, idevice::Device,
services::house_arrest::HouseArrest, services::lockdownd::LockdowndService,
};
pub struct AfcClient<'a> {
pub(crate) pointer: unsafe_bindings::afc_client_t,
phantom: std::marker::PhantomData<&'a Device>,
}
impl AfcClient<'_> {
pub fn new(device: &Device) -> Result<(Self, LockdowndService), String> {
let mut pointer = unsafe { std::mem::zeroed() };
let mut client_pointer = unsafe { std::mem::zeroed() };
let result = unsafe {
unsafe_bindings::afc_client_new(device.pointer, &mut pointer, &mut client_pointer)
};
if result != 0 {
return Err(format!("afc_client_new failed: {}", result));
}
Ok((
AfcClient {
pointer: client_pointer,
phantom: std::marker::PhantomData,
},
LockdowndService {
pointer: &mut pointer,
port: pointer.port as u32,
phantom: std::marker::PhantomData,
},
))
}
pub fn with_service(device: &Device, descriptor: LockdowndService) -> Result<Self, String> {
let mut client_pointer = unsafe { std::mem::zeroed() };
let result = unsafe {
unsafe_bindings::afc_client_new(device.pointer, descriptor.pointer, &mut client_pointer)
};
if result != 0 {
return Err(format!("afc_client_new failed: {}", result));
}
Ok(AfcClient {
pointer: client_pointer,
phantom: std::marker::PhantomData,
})
}
pub fn start_service(
device: &Device,
service_name: impl Into<String>,
) -> Result<Self, AfcError> {
let service_name = service_name.into();
let mut pointer = unsafe { std::mem::zeroed() };
let result = unsafe {
unsafe_bindings::afc_client_start_service(
device.pointer,
&mut pointer,
service_name.as_ptr() as *const c_char,
)
}
.into();
if result != AfcError::Success {
return Err(result);
}
Ok(AfcClient {
pointer,
phantom: std::marker::PhantomData,
})
}
pub fn get_device_info(&self) -> Result<String, AfcError> {
let mut info = unsafe { std::mem::zeroed() };
let mut info_ptr: *mut *mut c_char = &mut info;
let result =
unsafe { unsafe_bindings::afc_get_device_info(self.pointer, &mut info_ptr) }.into();
if result != AfcError::Success {
return Err(result);
}
Ok(unsafe { CStr::from_ptr(info) }
.to_string_lossy()
.into_owned())
}
pub fn read_directory(&self, directory: impl Into<String>) -> Result<Vec<String>, AfcError> {
let directory = directory.into();
if directory.is_empty() {
warn!("Cannot use empty string as directory");
return Err(AfcError::InvalidArg);
}
let directory_ptr: *const c_char = directory.as_ptr() as *const c_char;
let mut list: *mut *mut libc::c_char = std::ptr::null_mut::<*mut libc::c_char>();
let result =
unsafe { unsafe_bindings::afc_read_directory(self.pointer, directory_ptr, &mut list) }
.into();
if result != AfcError::Success {
return Err(result);
}
let mut list_vec: Vec<String> = Vec::new();
let mut list_ptr: *mut *mut libc::c_char = list;
while !list_ptr.is_null() {
if unsafe { *list_ptr }.is_null() {
break;
}
let list_str = unsafe { CStr::from_ptr(*list_ptr).to_string_lossy().into_owned() };
list_vec.push(list_str);
list_ptr = unsafe { list_ptr.offset(1) };
}
unsafe { unsafe_bindings::afc_dictionary_free(list) };
Ok(list_vec)
}
pub fn get_file_info(
&self,
path: impl Into<String>,
) -> Result<HashMap<String, String>, AfcError> {
let path = path.into();
if path.is_empty() {
warn!("Cannot use empty string as directory");
return Err(AfcError::InvalidArg);
}
let path_ptr: *const c_char = path.as_ptr() as *const c_char;
let mut list: *mut *mut libc::c_char = std::ptr::null_mut::<*mut libc::c_char>();
let result =
unsafe { unsafe_bindings::afc_get_file_info(self.pointer, path_ptr, &mut list) }.into();
if result != AfcError::Success {
return Err(result);
}
let mut list_vec: Vec<String> = Vec::new();
let mut list_ptr: *mut *mut libc::c_char = list;
while !list_ptr.is_null() {
if unsafe { *list_ptr }.is_null() {
break;
}
let list_str = unsafe { CStr::from_ptr(*list_ptr).to_string_lossy().into_owned() };
list_vec.push(list_str);
list_ptr = unsafe { list_ptr.offset(1) };
}
unsafe { unsafe_bindings::afc_dictionary_free(list) };
let mut ret_properties = HashMap::new();
while list_vec.len() > 1 {
ret_properties.insert(list_vec.remove(0), list_vec.remove(0));
}
Ok(ret_properties)
}
pub fn file_open(&self, path: impl Into<String>, mode: AfcFileMode) -> Result<u64, AfcError> {
let path: String = path.into();
let c_path = unsafe { CString::from_vec_unchecked(path.as_bytes().to_vec()) };
let mut handle = unsafe { std::mem::zeroed() };
let result = unsafe {
unsafe_bindings::afc_file_open(self.pointer, c_path.as_ptr(), mode.into(), &mut handle)
}
.into();
if result != AfcError::Success {
return Err(result);
}
Ok(handle)
}
pub fn file_close(&self, handle: u64) -> Result<(), AfcError> {
let result = unsafe { unsafe_bindings::afc_file_close(self.pointer, handle) }.into();
if result != AfcError::Success {
return Err(result);
}
Ok(())
}
pub fn file_lock(&self, handle: u64, lock_type: AfcLockOp) -> Result<(), AfcError> {
let result =
unsafe { unsafe_bindings::afc_file_lock(self.pointer, handle, lock_type.into()) }
.into();
if result != AfcError::Success {
return Err(result);
}
Ok(())
}
pub fn file_read(&self, handle: u64, length: u32) -> Result<Vec<i8>, AfcError> {
let mut buffer = vec![0i8; length as usize].into_boxed_slice();
let mut bytes_written = unsafe { std::mem::zeroed() };
let result = unsafe {
unsafe_bindings::afc_file_read(
self.pointer,
handle,
buffer.as_mut_ptr(),
length,
&mut bytes_written,
)
}
.into();
if result != AfcError::Success {
return Err(result);
}
let mut vec = buffer.into_vec();
vec.truncate(bytes_written as usize);
Ok(vec)
}
pub fn file_write(&self, handle: u64, data: Vec<u8>) -> Result<(), AfcError> {
let data: Vec<c_char> = data.into_iter().map(|x| x as c_char).collect();
let data_ptr: *const c_char = data.as_ptr() as *const c_char;
let mut bytes_written = unsafe { std::mem::zeroed() };
let result = unsafe {
unsafe_bindings::afc_file_write(
self.pointer,
handle,
data_ptr,
data.len() as u32,
&mut bytes_written,
)
}
.into();
if result != AfcError::Success {
return Err(result);
}
Ok(())
}
pub fn file_seek(&self, handle: u64, offset: i64, whence: u8) -> Result<(), AfcError> {
let result =
unsafe { unsafe_bindings::afc_file_seek(self.pointer, handle, offset, whence.into()) }
.into();
if result != AfcError::Success {
return Err(result);
}
Ok(())
}
pub fn file_tell(&self, handle: u64) -> Result<u64, AfcError> {
let mut position = unsafe { std::mem::zeroed() };
let result =
unsafe { unsafe_bindings::afc_file_tell(self.pointer, handle, &mut position) }.into();
if result != AfcError::Success {
return Err(result);
}
Ok(position)
}
pub fn file_truncate(&self, handle: u64, length: u64) -> Result<(), AfcError> {
let result =
unsafe { unsafe_bindings::afc_file_truncate(self.pointer, handle, length) }.into();
if result != AfcError::Success {
return Err(result);
}
Ok(())
}
pub fn remove_path(&self, path: impl Into<String>) -> Result<(), AfcError> {
let path: String = path.into();
let c_path = unsafe { CString::from_vec_unchecked(path.as_bytes().to_vec()) };
let result =
unsafe { unsafe_bindings::afc_remove_path(self.pointer, c_path.as_ptr()) }.into();
if result != AfcError::Success {
return Err(result);
}
Ok(())
}
pub fn rename_path(
&self,
old_path: impl Into<String>,
new_path: impl Into<String>,
) -> Result<(), AfcError> {
let old_path_ptr: *const c_char = old_path.into().as_ptr() as *const c_char;
let new_path_ptr: *const c_char = new_path.into().as_ptr() as *const c_char;
let result =
unsafe { unsafe_bindings::afc_rename_path(self.pointer, old_path_ptr, new_path_ptr) }
.into();
if result != AfcError::Success {
return Err(result);
}
Ok(())
}
pub fn make_directory(&self, path: impl Into<String>) -> Result<(), AfcError> {
let path: String = path.into();
let c_path = unsafe { CString::from_vec_unchecked(path.as_bytes().to_vec()) };
let result =
unsafe { unsafe_bindings::afc_make_directory(self.pointer, c_path.as_ptr()) }.into();
if result != AfcError::Success {
return Err(result);
}
Ok(())
}
pub fn truncate(&self, path: impl Into<String>, length: u64) -> Result<(), AfcError> {
let path_ptr: *const c_char = path.into().as_ptr() as *const c_char;
let result =
unsafe { unsafe_bindings::afc_truncate(self.pointer, path_ptr, length) }.into();
if result != AfcError::Success {
return Err(result);
}
Ok(())
}
pub fn make_link(
&self,
target: impl Into<String>,
link_type: LinkType,
link_path: impl Into<String>,
) -> Result<(), AfcError> {
let target_ptr: *const c_char = target.into().as_ptr() as *const c_char;
let link_name_ptr: *const c_char = link_path.into().as_ptr() as *const c_char;
let result = unsafe {
unsafe_bindings::afc_make_link(
self.pointer,
link_type.into(),
target_ptr,
link_name_ptr,
)
}
.into();
if result != AfcError::Success {
return Err(result);
}
Ok(())
}
pub fn set_file_time(&self, path: impl Into<String>, mtime: u64) -> Result<(), AfcError> {
let path_ptr: *const c_char = path.into().as_ptr() as *const c_char;
let result =
unsafe { unsafe_bindings::afc_set_file_time(self.pointer, path_ptr, mtime) }.into();
if result != AfcError::Success {
return Err(result);
}
Ok(())
}
pub fn remove_path_and_contents(&self, path: impl Into<String>) -> Result<(), AfcError> {
let path_ptr: *const c_char = path.into().as_ptr() as *const c_char;
let result =
unsafe { unsafe_bindings::afc_remove_path_and_contents(self.pointer, path_ptr) }.into();
if result != AfcError::Success {
return Err(result);
}
Ok(())
}
pub fn get_device_info_key(&self, key: impl Into<String>) -> Result<String, AfcError> {
let key_ptr: *const c_char = key.into().as_ptr() as *const c_char;
let mut value_ptr = unsafe { std::mem::zeroed() };
let result = unsafe {
unsafe_bindings::afc_get_device_info_key(self.pointer, key_ptr, &mut value_ptr)
}
.into();
if result != AfcError::Success {
return Err(result);
}
Ok(unsafe { CStr::from_ptr(value_ptr) }
.to_string_lossy()
.into_owned())
}
}
impl TryFrom<HouseArrest<'_>> for AfcClient<'_> {
type Error = AfcError;
fn try_from(house_arrest: HouseArrest<'_>) -> Result<Self, Self::Error> {
let mut to_fill = unsafe { std::mem::zeroed() };
let result = unsafe {
unsafe_bindings::afc_client_new_from_house_arrest_client(
house_arrest.pointer,
&mut to_fill,
)
}
.into();
if result != AfcError::Success {
return Err(result);
}
Ok(Self {
pointer: to_fill,
phantom: std::marker::PhantomData,
})
}
}
pub enum AfcFileMode {
ReadOnly,
ReadWrite,
WriteOnly,
WriteRead,
Append,
ReadAppend,
}
impl From<i8> for AfcFileMode {
fn from(mode: i8) -> Self {
match mode {
1 => AfcFileMode::ReadOnly,
2 => AfcFileMode::ReadWrite,
3 => AfcFileMode::WriteOnly,
4 => AfcFileMode::WriteRead,
5 => AfcFileMode::Append,
6 => AfcFileMode::ReadAppend,
_ => panic!("Invalid file mode"),
}
}
}
impl From<AfcFileMode> for u32 {
fn from(mode: AfcFileMode) -> Self {
match mode {
AfcFileMode::ReadOnly => 1,
AfcFileMode::ReadWrite => 2,
AfcFileMode::WriteOnly => 3,
AfcFileMode::WriteRead => 4,
AfcFileMode::Append => 5,
AfcFileMode::ReadAppend => 6,
}
}
}
pub enum AfcLockOp {
Sh,
Ex,
Un,
}
impl From<AfcLockOp> for u32 {
fn from(op: AfcLockOp) -> Self {
match op {
AfcLockOp::Sh => 5,
AfcLockOp::Ex => 6,
AfcLockOp::Un => 12,
}
}
}
pub enum LinkType {
HardLink,
SymbolicLink,
}
impl From<LinkType> for u32 {
fn from(link_type: LinkType) -> Self {
match link_type {
LinkType::HardLink => 1,
LinkType::SymbolicLink => 2,
}
}
}
impl Drop for AfcClient<'_> {
fn drop(&mut self) {
unsafe {
unsafe_bindings::afc_client_free(self.pointer);
}
}
}