diff --git a/src/heartbeat.rs b/src/heartbeat.rs new file mode 100644 index 0000000..b36a904 --- /dev/null +++ b/src/heartbeat.rs @@ -0,0 +1,46 @@ +// Jackson Coxson +// Abstractions for the heartbeat service on iOS + +use crate::{Idevice, IdeviceError}; + +pub struct HeartbeatClient { + pub idevice: Idevice, +} + +impl HeartbeatClient { + pub fn new(idevice: Idevice) -> Self { + Self { idevice } + } + + pub fn get_marco(&mut self) -> Result { + let rec = self.idevice.read_plist()?; + match rec.get("Interval") { + Some(plist::Value::Integer(interval)) => { + if let Some(interval) = interval.as_unsigned() { + Ok(interval) + } else { + Err(IdeviceError::UnexpectedResponse) + } + } + _ => match rec.get("Command") { + Some(plist::Value::String(command)) => { + if command.as_str() == "SleepyTime" { + Err(IdeviceError::HeartbeatSleepyTime) + } else { + Err(IdeviceError::UnexpectedResponse) + } + } + _ => Err(IdeviceError::UnexpectedResponse), + }, + } + } + + pub fn send_polo(&mut self) -> Result<(), IdeviceError> { + let mut req = plist::Dictionary::new(); + req.insert("Command".into(), "Polo".into()); + self.idevice + .send_plist(plist::Value::Dictionary(req.clone())) + .unwrap(); + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index aedca44..c8a6dd2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,38 +1,27 @@ // Jackson Coxson -const LOCKDOWND_PORT: u16 = 62078; - +pub mod heartbeat; +pub mod lockdownd; mod pairing_file; use log::{debug, error}; use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; -use serde::{Deserialize, Serialize}; use std::io::{self, BufWriter, Read, Write}; use thiserror::Error; trait ReadWrite: Read + Write + std::fmt::Debug {} impl ReadWrite for T {} -pub struct LockdowndClient { +pub struct Idevice { socket: Option>, // in a box for now to use the ReadWrite trait for further uses label: String, } -#[derive(Serialize, Deserialize)] -#[serde(rename_all = "PascalCase")] -struct LockdowndRequest { - label: String, - key: Option, - request: String, -} - -impl LockdowndClient { +impl Idevice { pub fn get_type(&mut self) -> Result { - let req = LockdowndRequest { - label: self.label.clone(), - key: None, - request: "QueryType".to_string(), - }; + let mut req = plist::Dictionary::new(); + req.insert("Label".into(), self.label.clone().into()); + req.insert("Request".into(), "QueryType".into()); let message = plist::to_value(&req)?; self.send_plist(message)?; let message: plist::Dictionary = self.read_plist()?; @@ -42,36 +31,6 @@ impl LockdowndClient { } } - pub fn get_value(&mut self, value: impl Into) -> Result { - let req = LockdowndRequest { - label: self.label.clone(), - key: Some(value.into()), - request: "GetValue".to_string(), - }; - let message = plist::to_value(&req)?; - self.send_plist(message)?; - let message: plist::Dictionary = self.read_plist()?; - match message.get("Value") { - Some(m) => Ok(plist::from_value(m)?), - None => Err(IdeviceError::UnexpectedResponse), - } - } - - pub fn get_all_values(&mut self) -> Result { - let req = LockdowndRequest { - label: self.label.clone(), - key: None, - request: "GetValue".to_string(), - }; - let message = plist::to_value(&req)?; - self.send_plist(message)?; - let message: plist::Dictionary = self.read_plist()?; - match message.get("Value") { - Some(m) => Ok(plist::from_value(m)?), - None => Err(IdeviceError::UnexpectedResponse), - } - } - /// Sends a plist to the socket fn send_plist(&mut self, message: plist::Value) -> Result<(), IdeviceError> { if let Some(socket) = &mut self.socket { @@ -115,48 +74,11 @@ impl LockdowndClient { } } - /// Starts a TLS session with the client + /// Wraps current connection in TLS pub fn start_session( &mut self, pairing_file: pairing_file::PairingFile, ) -> Result<(), IdeviceError> { - if self.socket.is_none() { - return Err(IdeviceError::NoEstablishedConnection); - } - - let mut request = plist::Dictionary::new(); - request.insert( - "Label".to_string(), - plist::Value::String(self.label.clone()), - ); - - request.insert( - "Request".to_string(), - plist::Value::String("StartSession".to_string()), - ); - request.insert( - "HostID".to_string(), - plist::Value::String(pairing_file.host_id.clone()), - ); - request.insert( - "SystemBUID".to_string(), - plist::Value::String(pairing_file.system_buid.clone()), - ); - - self.send_plist(plist::Value::Dictionary(request))?; - - let response = self.read_plist()?; - match response.get("EnableSessionSSL") { - Some(plist::Value::Boolean(enable)) => { - if !enable { - return Err(IdeviceError::UnexpectedResponse); - } - } - _ => { - return Err(IdeviceError::UnexpectedResponse); - } - } - let mut connector = SslConnector::builder(SslMethod::tls()).unwrap(); connector .set_certificate(&pairing_file.host_certificate) @@ -173,44 +95,6 @@ impl LockdowndClient { Ok(()) } - - /// Asks lockdownd to pretty please start a service for us - /// # Arguments - /// `identifier` - The identifier for the service you want to start - /// # Returns - /// The port number and whether to enable SSL on success, `IdeviceError` on failure - pub fn start_service( - &mut self, - identifier: impl Into, - ) -> Result<(u16, bool), IdeviceError> { - let identifier = identifier.into(); - let mut req = plist::Dictionary::new(); - req.insert("Request".into(), "StartService".into()); - req.insert("Service".into(), identifier.into()); - self.send_plist(plist::Value::Dictionary(req))?; - let response = self.read_plist()?; - println!("{response:?}"); - match response.get("EnableServiceSSL") { - Some(plist::Value::Boolean(ssl)) => match response.get("Port") { - Some(plist::Value::Integer(port)) => { - if let Some(port) = port.as_unsigned() { - Ok((port as u16, *ssl)) - } else { - error!("Port isn't an unsiged integer!"); - Err(IdeviceError::UnexpectedResponse) - } - } - _ => { - error!("Response didn't contain an integer port"); - Err(IdeviceError::UnexpectedResponse) - } - }, - _ => { - error!("Response didn't contain EnableServiceSSL bool!"); - Err(IdeviceError::UnexpectedResponse) - } - } - } } #[derive(Error, Debug)] @@ -227,6 +111,8 @@ pub enum IdeviceError { GetProhibited, #[error("no established connection")] NoEstablishedConnection, + #[error("device went to sleep")] + HeartbeatSleepyTime, #[error("unknown error `{0}` returned from device")] UnknownErrorType(String), } diff --git a/src/lockdownd.rs b/src/lockdownd.rs new file mode 100644 index 0000000..bebe483 --- /dev/null +++ b/src/lockdownd.rs @@ -0,0 +1,146 @@ +// Jackson Coxson +// Abstractions for lockdownd + +pub const LOCKDOWND_PORT: u16 = 62078; + +use log::error; +use serde::{Deserialize, Serialize}; + +use crate::{pairing_file, Idevice, IdeviceError}; + +pub struct LockdowndClient { + pub idevice: crate::Idevice, +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "PascalCase")] +struct LockdowndRequest { + label: String, + key: Option, + request: String, +} + +impl LockdowndClient { + pub fn new(idevice: Idevice) -> Self { + Self { idevice } + } + pub fn get_value(&mut self, value: impl Into) -> Result { + let req = LockdowndRequest { + label: self.idevice.label.clone(), + key: Some(value.into()), + request: "GetValue".to_string(), + }; + let message = plist::to_value(&req)?; + self.idevice.send_plist(message)?; + let message: plist::Dictionary = self.idevice.read_plist()?; + match message.get("Value") { + Some(m) => Ok(plist::from_value(m)?), + None => Err(IdeviceError::UnexpectedResponse), + } + } + + pub fn get_all_values(&mut self) -> Result { + let req = LockdowndRequest { + label: self.idevice.label.clone(), + key: None, + request: "GetValue".to_string(), + }; + let message = plist::to_value(&req)?; + self.idevice.send_plist(message)?; + let message: plist::Dictionary = self.idevice.read_plist()?; + match message.get("Value") { + Some(m) => Ok(plist::from_value(m)?), + None => Err(IdeviceError::UnexpectedResponse), + } + } + + /// Starts a TLS session with the client + pub fn start_session( + &mut self, + pairing_file: pairing_file::PairingFile, + ) -> Result<(), IdeviceError> { + if self.idevice.socket.is_none() { + return Err(IdeviceError::NoEstablishedConnection); + } + + let mut request = plist::Dictionary::new(); + request.insert( + "Label".to_string(), + plist::Value::String(self.idevice.label.clone()), + ); + + request.insert( + "Request".to_string(), + plist::Value::String("StartSession".to_string()), + ); + request.insert( + "HostID".to_string(), + plist::Value::String(pairing_file.host_id.clone()), + ); + request.insert( + "SystemBUID".to_string(), + plist::Value::String(pairing_file.system_buid.clone()), + ); + + self.idevice.send_plist(plist::Value::Dictionary(request))?; + + let response = self.idevice.read_plist()?; + match response.get("EnableSessionSSL") { + Some(plist::Value::Boolean(enable)) => { + if !enable { + return Err(IdeviceError::UnexpectedResponse); + } + } + _ => { + return Err(IdeviceError::UnexpectedResponse); + } + } + + self.idevice.start_session(pairing_file)?; + Ok(()) + } + + /// Asks lockdownd to pretty please start a service for us + /// # Arguments + /// `identifier` - The identifier for the service you want to start + /// # Returns + /// The port number and whether to enable SSL on success, `IdeviceError` on failure + pub fn start_service( + &mut self, + identifier: impl Into, + ) -> Result<(u16, bool), IdeviceError> { + let identifier = identifier.into(); + let mut req = plist::Dictionary::new(); + req.insert("Request".into(), "StartService".into()); + req.insert("Service".into(), identifier.into()); + self.idevice.send_plist(plist::Value::Dictionary(req))?; + let response = self.idevice.read_plist()?; + println!("{response:?}"); + match response.get("EnableServiceSSL") { + Some(plist::Value::Boolean(ssl)) => match response.get("Port") { + Some(plist::Value::Integer(port)) => { + if let Some(port) = port.as_unsigned() { + Ok((port as u16, *ssl)) + } else { + error!("Port isn't an unsiged integer!"); + Err(IdeviceError::UnexpectedResponse) + } + } + _ => { + error!("Response didn't contain an integer port"); + Err(IdeviceError::UnexpectedResponse) + } + }, + _ => { + error!("Response didn't contain EnableServiceSSL bool!"); + Err(IdeviceError::UnexpectedResponse) + } + } + } +} + +impl From for LockdowndClient { + fn from(value: Idevice) -> Self { + Self::new(value) + } +}