pub mod dfu;
pub mod mode;
use std::{future::Future, pin::Pin};
use tracing::debug;
pub use mode::{DeviceInfo, Mode};
use crate::{IdeviceError, services::restore::RestoreError};
pub const USB_TIMEOUT_MS: u32 = 10_000;
pub const TRANSFER_SIZE_RECOVERY: usize = 0x8000;
pub const TRANSFER_SIZE_DFU: usize = 0x800;
pub type RecoveryFuture<'a, T> = Pin<Box<dyn Future<Output = Result<T, IdeviceError>> + Send + 'a>>;
#[derive(Debug, Clone, Copy)]
pub struct ControlSetup {
pub request_type: u8,
pub request: u8,
pub value: u16,
pub index: u16,
}
impl ControlSetup {
pub const fn new(request_type: u8, request: u8, value: u16, index: u16) -> Self {
Self {
request_type,
request,
value,
index,
}
}
}
pub trait RecoveryTransport: Send + Sync + std::fmt::Debug {
fn control_out<'a>(
&'a mut self,
setup: ControlSetup,
data: &'a [u8],
timeout_ms: u32,
) -> RecoveryFuture<'a, usize>;
fn control_in<'a>(
&'a mut self,
setup: ControlSetup,
length: u16,
timeout_ms: u32,
) -> RecoveryFuture<'a, Vec<u8>>;
fn bulk_out<'a>(
&'a mut self,
endpoint: u8,
data: &'a [u8],
timeout_ms: u32,
) -> RecoveryFuture<'a, usize>;
fn serial_number(&mut self) -> RecoveryFuture<'_, String>;
fn product_id(&self) -> u16;
fn set_configuration(&mut self, configuration: u8) -> RecoveryFuture<'_, ()>;
fn claim_interface(&mut self, interface: u8, alt_setting: u8) -> RecoveryFuture<'_, ()>;
fn reset(&mut self) -> RecoveryFuture<'_, ()>;
}
#[derive(Debug)]
pub struct RecoveryDevice {
transport: Box<dyn RecoveryTransport>,
mode: Mode,
info: DeviceInfo,
}
impl RecoveryDevice {
pub async fn new(mut transport: Box<dyn RecoveryTransport>) -> Result<Self, IdeviceError> {
let mode = Mode::from_product_id(transport.product_id()).ok_or_else(|| {
IdeviceError::Restore(RestoreError::Recovery(format!(
"not an Apple recovery/DFU product id: {:#06x}",
transport.product_id()
)))
})?;
let serial = transport.serial_number().await?;
let info = DeviceInfo::parse(&serial);
debug!("recovery device: mode={mode:?} info={info:?}");
let mut dev = Self {
transport,
mode,
info,
};
dev.configure().await?;
Ok(dev)
}
async fn configure(&mut self) -> Result<(), IdeviceError> {
self.transport.set_configuration(1).await?;
self.transport.claim_interface(0, 0).await?;
if self.mode.is_recovery() && self.mode.product_id() > Mode::Recovery2.product_id() {
self.transport.claim_interface(1, 1).await?;
}
Ok(())
}
pub fn mode(&self) -> Mode {
self.mode
}
pub fn info(&self) -> &DeviceInfo {
&self.info
}
pub async fn send_command_with_request(
&mut self,
command: &str,
b_request: u8,
) -> Result<(), IdeviceError> {
debug!("recovery command (req {b_request}): {command}");
let mut data = command.as_bytes().to_vec();
data.push(0);
self.transport
.control_out(
ControlSetup::new(0x40, b_request, 0, 0),
&data,
USB_TIMEOUT_MS,
)
.await?;
Ok(())
}
pub async fn send_command(&mut self, command: &str) -> Result<(), IdeviceError> {
self.send_command_with_request(command, 0).await
}
pub async fn finish_transfer(&mut self) -> Result<(), IdeviceError> {
self.transport
.control_out(ControlSetup::new(0x21, 1, 0, 0), &[], USB_TIMEOUT_MS)
.await?;
Ok(())
}
pub async fn getenv(&mut self, name: &str) -> Result<Vec<u8>, IdeviceError> {
self.send_command(&format!("getenv {name}")).await?;
self.transport
.control_in(ControlSetup::new(0xC0, 0, 0, 0), 255, USB_TIMEOUT_MS)
.await
}
pub async fn setenv(&mut self, name: &str, value: &str) -> Result<(), IdeviceError> {
self.send_command(&format!("setenv {name} {value}")).await
}
pub async fn set_autoboot(&mut self, enable: bool) -> Result<(), IdeviceError> {
self.setenv("auto-boot", if enable { "true" } else { "false" })
.await?;
self.send_command("saveenv").await
}
pub async fn reboot(&mut self) -> Result<(), IdeviceError> {
self.send_command("reboot").await
}
pub async fn send_buffer(&mut self, buf: &[u8]) -> Result<(), IdeviceError> {
if self.mode.is_recovery() {
self.send_buffer_recovery(buf).await
} else {
dfu::send_buffer_dfu(self, buf).await
}
}
async fn send_buffer_recovery(&mut self, buf: &[u8]) -> Result<(), IdeviceError> {
self.transport
.control_out(ControlSetup::new(0x41, 0, 0, 0), &[], USB_TIMEOUT_MS)
.await?;
for chunk in buf.chunks(TRANSFER_SIZE_RECOVERY) {
let n = self.transport.bulk_out(0x04, chunk, USB_TIMEOUT_MS).await?;
if n != chunk.len() {
return Err(IdeviceError::Restore(RestoreError::Recovery(format!(
"recovery upload short write: {n} of {}",
chunk.len()
))));
}
}
Ok(())
}
pub(crate) fn transport(&mut self) -> &mut dyn RecoveryTransport {
&mut *self.transport
}
pub(crate) async fn dfu_status(&mut self) -> Result<u8, IdeviceError> {
let resp = self
.transport
.control_in(ControlSetup::new(0xA1, 3, 0, 0), 6, USB_TIMEOUT_MS)
.await?;
resp.get(4).copied().ok_or_else(|| {
IdeviceError::Restore(RestoreError::Recovery(
"short DFU GETSTATUS response".into(),
))
})
}
}