use plist_macro::plist_to_xml_bytes;
use serde::Deserialize;
use tracing::warn;
use crate::{IdeviceError, ReadWrite, RsdService, obf, xpc::XPCObject};
use super::CoreDeviceServiceClient;
impl RsdService for AppServiceClient<Box<dyn ReadWrite>> {
fn rsd_service_name() -> std::borrow::Cow<'static, str> {
obf!("com.apple.coredevice.appservice")
}
async fn from_stream(stream: Box<dyn ReadWrite>) -> Result<Self, IdeviceError> {
Ok(Self {
inner: CoreDeviceServiceClient::new(stream).await?,
})
}
}
#[derive(Debug)]
pub struct AppServiceClient<R: ReadWrite> {
inner: CoreDeviceServiceClient<R>,
}
#[derive(Deserialize, Clone, Debug)]
pub struct AppListEntry {
#[serde(rename = "isRemovable")]
pub is_removable: bool,
pub name: String,
#[serde(rename = "isFirstParty")]
pub is_first_party: bool,
pub path: String,
#[serde(rename = "bundleIdentifier")]
pub bundle_identifier: String,
#[serde(rename = "isDeveloperApp")]
pub is_developer_app: bool,
#[serde(rename = "bundleVersion")]
pub bundle_version: Option<String>,
#[serde(rename = "isInternal")]
pub is_internal: bool,
#[serde(rename = "isHidden")]
pub is_hidden: bool,
#[serde(rename = "isAppClip")]
pub is_app_clip: bool,
pub version: Option<String>,
}
#[derive(Deserialize, Clone, Debug)]
pub struct LaunchResponse {
#[serde(rename = "processIdentifierVersion")]
pub process_identifier_version: u32,
#[serde(rename = "processIdentifier")]
pub pid: u32,
#[serde(rename = "executableURL")]
pub executable_url: ExecutableUrl,
#[serde(rename = "auditToken")]
pub audit_token: Vec<u32>,
}
#[derive(Deserialize, Clone, Debug)]
pub struct ExecutableUrl {
pub relative: String,
}
#[derive(Deserialize, Clone, Debug)]
pub struct ProcessToken {
#[serde(rename = "processIdentifier")]
pub pid: u32,
#[serde(rename = "executableURL")]
pub executable_url: Option<ExecutableUrl>,
}
#[derive(Deserialize, Clone, Debug)]
pub struct SignalResponse {
pub process: ProcessToken,
#[serde(rename = "deviceTimestamp")]
pub device_timestamp: plist::Date,
pub signal: u32,
}
#[derive(Deserialize, Clone, Debug)]
pub struct IconData {
pub data: plist::Data,
#[serde(rename = "iconSize.height")]
pub icon_height: f64,
#[serde(rename = "iconSize.width")]
pub icon_width: f64,
#[serde(rename = "minimumSize.height")]
pub minimum_height: f64,
#[serde(rename = "minimumSize.width")]
pub minimum_width: f64,
#[serde(rename = "$classes")]
pub classes: Vec<String>,
#[serde(rename = "validationToken")]
pub validation_token: plist::Data,
pub uuid: IconUuid,
}
#[derive(Deserialize, Clone, Debug)]
pub struct IconUuid {
#[serde(rename = "NS.uuidbytes")]
pub bytes: plist::Data,
#[serde(rename = "$classes")]
pub classes: Vec<String>,
}
impl<R: ReadWrite> AppServiceClient<R> {
pub async fn new(stream: R) -> Result<Self, IdeviceError> {
Ok(Self {
inner: CoreDeviceServiceClient::new(stream).await?,
})
}
pub async fn list_apps(
&mut self,
app_clips: bool,
removable_apps: bool,
hidden_apps: bool,
internal_apps: bool,
default_apps: bool,
) -> Result<Vec<AppListEntry>, IdeviceError> {
let options = crate::plist!(dict {
"includeAppClips": app_clips,
"includeRemovableApps": removable_apps,
"includeHiddenApps": hidden_apps,
"includeInternalApps": internal_apps,
"includeDefaultApps": default_apps,
});
let res = self
.inner
.invoke_with_plist("com.apple.coredevice.feature.listapps", options)
.await?;
let res = match res.as_array() {
Some(a) => a,
None => {
warn!("CoreDevice result was not an array");
return Err(IdeviceError::UnexpectedResponse(
"list apps result was not an array".into(),
));
}
};
let mut desd = Vec::new();
for r in res {
let r: AppListEntry = match plist::from_value(r) {
Ok(r) => r,
Err(e) => {
warn!("Failed to parse app entry: {e:?}");
return Err(IdeviceError::UnexpectedResponse(
"failed to parse app list entry".into(),
));
}
};
desd.push(r);
}
Ok(desd)
}
#[allow(clippy::too_many_arguments)] pub async fn launch_application(
&mut self,
bundle_id: impl Into<String>,
arguments: &[&str],
kill_existing: bool,
start_suspended: bool,
environment: Option<plist::Dictionary>,
platform_options: Option<plist::Dictionary>,
stdio_uuid: Option<uuid::Uuid>,
) -> Result<LaunchResponse, IdeviceError> {
let bundle_id = bundle_id.into();
let req = crate::plist!({
"applicationSpecifier": {
"bundleIdentifier": {
"_0": bundle_id
}
},
"options": {
"arguments": arguments,
"environmentVariables": environment.unwrap_or_default(),
"standardIOUsesPseudoterminals": true,
"startStopped": start_suspended,
"terminateExisting": kill_existing,
"user": {
"active": true,
},
"platformSpecificOptions": plist::Value::Data(plist_to_xml_bytes(&platform_options.unwrap_or_default())),
},
});
let req: XPCObject = req.into();
let mut req = req.to_dictionary().unwrap();
req.insert(
"standardIOIdentifiers".into(),
match stdio_uuid {
Some(u) => {
let u = XPCObject::Uuid(u);
let mut d = crate::xpc::Dictionary::new();
d.insert("standardInput".into(), u.clone());
d.insert("standardOutput".into(), u.clone());
d.insert("standardError".into(), u.clone());
d.into()
}
None => crate::xpc::Dictionary::new().into(),
},
);
let res = self
.inner
.invoke("com.apple.coredevice.feature.launchapplication", Some(req))
.await?;
let res = match res
.as_dictionary()
.and_then(|r| r.get("processToken"))
.and_then(|x| plist::from_value(x).ok())
{
Some(r) => r,
None => {
warn!("CoreDevice res did not contain parsable processToken");
return Err(IdeviceError::UnexpectedResponse(
"missing or unparsable processToken in launch response".into(),
));
}
};
Ok(res)
}
pub async fn list_processes(&mut self) -> Result<Vec<ProcessToken>, IdeviceError> {
let res = self
.inner
.invoke("com.apple.coredevice.feature.listprocesses", None)
.await?;
let res = match res
.as_dictionary()
.and_then(|x| x.get("processTokens"))
.and_then(|x| plist::from_value(x).ok())
{
Some(r) => r,
None => {
warn!("CoreDevice res did not contain parsable processToken");
return Err(IdeviceError::UnexpectedResponse(
"missing or unparsable processTokens in list processes response".into(),
));
}
};
Ok(res)
}
pub async fn uninstall_app(
&mut self,
bundle_id: impl Into<String>,
) -> Result<(), IdeviceError> {
let bundle_id = bundle_id.into();
self.inner
.invoke_with_plist(
"com.apple.coredevice.feature.uninstallapp",
crate::plist!({"bundleIdentifier": bundle_id})
.into_dictionary()
.unwrap(),
)
.await?;
Ok(())
}
pub async fn send_signal(
&mut self,
pid: u32,
signal: u32,
) -> Result<SignalResponse, IdeviceError> {
let res = self
.inner
.invoke_with_plist(
"com.apple.coredevice.feature.sendsignaltoprocess",
crate::plist!({
"process": { "processIdentifier": pid as i64},
"signal": signal as i64,
})
.into_dictionary()
.unwrap(),
)
.await?;
let res = match plist::from_value(&res) {
Ok(r) => r,
Err(e) => {
warn!("Could not parse signal response: {e:?}");
return Err(IdeviceError::UnexpectedResponse(
"failed to parse signal response".into(),
));
}
};
Ok(res)
}
pub async fn fetch_app_icon(
&mut self,
bundle_id: impl Into<String>,
width: f32,
height: f32,
scale: f32,
allow_placeholder: bool,
) -> Result<IconData, IdeviceError> {
let bundle_id = bundle_id.into();
let res = self
.inner
.invoke_with_plist(
"com.apple.coredevice.feature.fetchappicons",
crate::plist!({
"width": width,
"height": height,
"scale": scale,
"allowPlaceholder": allow_placeholder,
"bundleIdentifier": bundle_id
})
.into_dictionary()
.unwrap(),
)
.await?;
let res = match res
.as_dictionary()
.and_then(|x| x.get("appIconContainer"))
.and_then(|x| x.as_dictionary())
.and_then(|x| x.get("iconImage"))
.and_then(|x| x.as_data())
{
Some(r) => r.to_vec(),
None => {
warn!("Did not receive appIconContainer/iconImage data");
return Err(IdeviceError::UnexpectedResponse(
"missing appIconContainer/iconImage data in fetch icon response".into(),
));
}
};
let res = ns_keyed_archive::decode::from_bytes(&res)
.map_err(crate::services::dvt::errors::DvtError::from)?;
match plist::from_value(&res) {
Ok(r) => Ok(r),
Err(e) => {
warn!("Failed to deserialize ns keyed archive: {e:?}");
Err(IdeviceError::UnexpectedResponse(
"failed to deserialize icon NSKeyedArchive".into(),
))
}
}
}
}