diff --git a/idevice/Cargo.toml b/idevice/Cargo.toml index c9d1735..a550591 100644 --- a/idevice/Cargo.toml +++ b/idevice/Cargo.toml @@ -69,6 +69,7 @@ misagent = [] mobile_image_mounter = ["dep:sha2"] location_simulation = [] pair = ["chrono/default", "tokio/time", "dep:sha2", "dep:rsa", "dep:x509-cert"] +rsd = ["xpc"] syslog_relay = ["dep:bytes"] tcp = ["tokio/net"] tunnel_tcp_stack = ["dep:rand", "dep:futures", "tokio/fs", "tokio/sync"] @@ -98,6 +99,7 @@ full = [ "usbmuxd", "xpc", "location_simulation", + "rsd", "tcp", "tunnel_tcp_stack", "tss", diff --git a/idevice/src/lib.rs b/idevice/src/lib.rs index cc8c4f7..d6a54dd 100644 --- a/idevice/src/lib.rs +++ b/idevice/src/lib.rs @@ -15,9 +15,12 @@ pub mod tunneld; #[cfg(feature = "usbmuxd")] pub mod usbmuxd; mod util; +#[cfg(feature = "xpc")] +pub mod xpc; pub mod services; pub use services::*; +pub use xpc::RemoteXpcClient; use log::{debug, error, trace}; use provider::IdeviceProvider; diff --git a/idevice/src/services/mod.rs b/idevice/src/services/mod.rs index a276190..1c035f4 100644 --- a/idevice/src/services/mod.rs +++ b/idevice/src/services/mod.rs @@ -23,9 +23,9 @@ pub mod misagent; pub mod mobile_image_mounter; #[cfg(feature = "syslog_relay")] pub mod os_trace_relay; +#[cfg(feature = "rsd")] +pub mod rsd; #[cfg(feature = "springboardservices")] pub mod springboardservices; #[cfg(feature = "syslog_relay")] pub mod syslog_relay; -#[cfg(feature = "xpc")] -pub mod xpc; diff --git a/idevice/src/services/rsd.rs b/idevice/src/services/rsd.rs new file mode 100644 index 0000000..c6b7dc8 --- /dev/null +++ b/idevice/src/services/rsd.rs @@ -0,0 +1,121 @@ +//! Remote Service Discovery +//! Communicates via XPC and returns advertised services + +use std::collections::HashMap; + +use log::warn; +use serde::Deserialize; + +use crate::{IdeviceError, ReadWrite, RemoteXpcClient}; + +/// Describes an available XPC service +#[derive(Debug, Clone, Deserialize)] +pub struct XPCService { + /// Required entitlement to access this service + pub entitlement: String, + /// Port number where the service is available + pub port: u16, + /// Whether the service uses remote XPC + pub uses_remote_xpc: bool, + /// Optional list of supported features + pub features: Option>, + /// Optional service version number + pub service_version: Option, +} + +pub struct RsdClient { + inner: RemoteXpcClient, +} + +impl RsdClient { + pub async fn new(socket: R) -> Result { + Ok(Self { + inner: RemoteXpcClient::new(socket).await?, + }) + } + + pub async fn get_services(&mut self) -> Result, IdeviceError> { + let data = self.inner.do_handshake().await?; + + let data = match data + .as_dictionary() + .and_then(|x| x.get("Services")) + .and_then(|x| x.as_dictionary()) + { + Some(d) => d, + None => return Err(IdeviceError::UnexpectedResponse), + }; + + // Parse available services + let mut services = HashMap::new(); + for (name, service) in data.into_iter() { + match service.as_dictionary() { + Some(service) => { + let entitlement = match service.get("Entitlement").and_then(|x| x.as_string()) { + Some(e) => e.to_string(), + None => { + warn!("Service did not contain entitlement string"); + continue; + } + }; + let port = match service + .get("Port") + .and_then(|x| x.as_string()) + .and_then(|x| x.parse::().ok()) + { + Some(e) => e, + None => { + warn!("Service did not contain port string"); + continue; + } + }; + let uses_remote_xpc = match service + .get("Properties") + .and_then(|x| x.as_dictionary()) + .and_then(|x| x.get("UsesRemoteXPC")) + .and_then(|x| x.as_boolean()) + { + Some(e) => e.to_owned(), + None => false, // default is false + }; + + let features = service + .get("Properties") + .and_then(|x| x.as_dictionary()) + .and_then(|x| x.get("Features")) + .and_then(|x| x.as_array()) + .map(|f| { + f.iter() + .filter_map(|x| x.as_string()) + .map(|x| x.to_string()) + .collect::>() + }); + + let service_version = service + .get("Properties") + .and_then(|x| x.as_dictionary()) + .and_then(|x| x.get("ServiceVersion")) + .and_then(|x| x.as_signed_integer()) + .map(|e| e.to_owned()); + + services.insert( + name.to_string(), + XPCService { + entitlement, + port, + uses_remote_xpc, + features, + service_version, + }, + ); + } + None => { + warn!("Service is not a dictionary!"); + continue; + } + } + } + + Ok(services) + } +} diff --git a/idevice/src/services/xpc/format.rs b/idevice/src/xpc/format.rs similarity index 100% rename from idevice/src/services/xpc/format.rs rename to idevice/src/xpc/format.rs diff --git a/idevice/src/services/xpc/http2/frame.rs b/idevice/src/xpc/http2/frame.rs similarity index 100% rename from idevice/src/services/xpc/http2/frame.rs rename to idevice/src/xpc/http2/frame.rs diff --git a/idevice/src/services/xpc/http2/mod.rs b/idevice/src/xpc/http2/mod.rs similarity index 100% rename from idevice/src/services/xpc/http2/mod.rs rename to idevice/src/xpc/http2/mod.rs diff --git a/idevice/src/services/xpc/mod.rs b/idevice/src/xpc/mod.rs similarity index 100% rename from idevice/src/services/xpc/mod.rs rename to idevice/src/xpc/mod.rs