Skip to main content

idevice/
lib.rs

1#![doc = include_str!("../README.md")]
2#![warn(missing_debug_implementations)]
3#![warn(missing_copy_implementations)]
4// Jackson Coxson
5
6#[cfg(all(feature = "pair", feature = "rustls"))]
7mod ca;
8pub mod cursor;
9mod obfuscation;
10pub mod pairing_file;
11pub mod provider;
12#[cfg(feature = "remote_pairing")]
13pub mod remote_pairing;
14#[cfg(feature = "rustls")]
15mod sni;
16#[cfg(feature = "tunnel_tcp_stack")]
17pub mod tcp;
18#[cfg(feature = "tss")]
19pub mod tss;
20#[cfg(feature = "tunneld")]
21pub mod tunneld;
22#[cfg(feature = "usbmuxd")]
23pub mod usbmuxd;
24pub mod utils;
25#[cfg(feature = "xpc")]
26pub mod xpc;
27
28pub mod services;
29pub use services::*;
30#[cfg(any(feature = "core_device_proxy", feature = "remote_pairing"))]
31pub mod tunnel;
32
33#[cfg(feature = "xpc")]
34pub use xpc::RemoteXpcClient;
35
36use plist_macro::{plist, pretty_print_dictionary, pretty_print_plist};
37use provider::{IdeviceProvider, RsdProvider};
38#[cfg(feature = "rustls")]
39use rustls::{crypto::CryptoProvider, pki_types::ServerName};
40use std::{
41    io::{self, BufWriter},
42    sync::Arc,
43};
44use thiserror::Error;
45use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
46use tracing::{debug, trace};
47
48use crate::services::lockdown::LockdownClient;
49
50/// A trait combining all required characteristics for a device communication socket
51///
52/// This serves as a convenience trait for any type that can be used as an asynchronous
53/// read/write socket for device communication. Combines common async I/O traits with
54/// thread safety and debugging requirements.
55///
56/// Tokio's TcpStream and UnixStream implement this trait.
57pub trait ReadWrite: AsyncRead + AsyncWrite + Unpin + Send + Sync + std::fmt::Debug {}
58
59// Blanket implementation for any compatible type
60impl<T: AsyncRead + AsyncWrite + Unpin + Send + Sync + std::fmt::Debug> ReadWrite for T {}
61
62/// Interface for services that can be connected to on an iOS device
63///
64/// Implement this trait to define new services that can be accessed through the
65/// device connection protocol.
66pub trait IdeviceService: Sized {
67    /// Returns the service name as advertised by the device
68    fn service_name() -> std::borrow::Cow<'static, str>;
69
70    /// Establishes a connection to this service
71    ///
72    /// # Arguments
73    /// * `provider` - The device provider that can supply connections
74    ///
75    // From the docs
76    // │ │ ├╴  use of `async fn` in public traits is discouraged as auto trait bounds cannot be specified
77    // │ │ │    you can suppress this lint if you plan to use the trait only in your own code, or do not care about auto traits like `Send` on the `Future`
78    // │ │ │    `#[warn(async_fn_in_trait)]` on by default rustc (async_fn_in_trait) [66, 5]
79    #[allow(async_fn_in_trait)]
80    async fn connect(provider: &dyn IdeviceProvider) -> Result<Self, IdeviceError> {
81        let mut lockdown = LockdownClient::connect(provider).await?;
82
83        #[cfg(feature = "openssl")]
84        let legacy = lockdown
85            .get_value(Some("ProductVersion"), None)
86            .await
87            .ok()
88            .as_ref()
89            .and_then(|x| x.as_string())
90            .and_then(|x| x.split(".").next())
91            .and_then(|x| x.parse::<u8>().ok())
92            .map(|x| x < 5)
93            .unwrap_or(false);
94
95        #[cfg(not(feature = "openssl"))]
96        let legacy = false;
97
98        lockdown
99            .start_session(&provider.get_pairing_file().await?)
100            .await?;
101        // Best-effort fetch UDID for downstream defaults (e.g., MobileBackup2 Target/Source identifiers)
102        let udid_value = match lockdown.get_value(Some("UniqueDeviceID"), None).await {
103            Ok(v) => v.as_string().map(|s| s.to_string()),
104            Err(_) => None,
105        };
106
107        let (port, ssl) = lockdown.start_service(Self::service_name()).await?;
108
109        let mut idevice = provider.connect(port).await?;
110        if ssl {
111            idevice
112                .start_session(&provider.get_pairing_file().await?, legacy)
113                .await?;
114        }
115
116        if let Some(udid) = udid_value {
117            idevice.set_udid(udid);
118        }
119
120        Self::from_stream(idevice).await
121    }
122
123    #[allow(async_fn_in_trait)]
124    async fn from_stream(idevice: Idevice) -> Result<Self, IdeviceError>;
125}
126
127#[cfg(feature = "rsd")]
128pub trait RsdService: Sized {
129    fn rsd_service_name() -> std::borrow::Cow<'static, str>;
130    fn from_stream(
131        stream: Box<dyn ReadWrite>,
132    ) -> impl std::future::Future<Output = Result<Self, IdeviceError>> + Send;
133    fn connect_rsd(
134        provider: &mut impl RsdProvider,
135        handshake: &mut rsd::RsdHandshake,
136    ) -> impl std::future::Future<Output = Result<Self, IdeviceError>>
137    where
138        Self: crate::RsdService,
139    {
140        handshake.connect(provider)
141    }
142}
143
144/// Type alias for boxed device connection sockets
145///
146/// Used to enable dynamic dispatch of different connection types while maintaining
147/// the required ReadWrite characteristics.
148pub type IdeviceSocket = Box<dyn ReadWrite>;
149
150/// Main handle for communicating with an iOS device
151///
152/// Manages the connection socket and provides methods for common device operations
153/// and message exchange.
154#[derive(Debug)]
155pub struct Idevice {
156    /// The underlying connection socket, boxed for dynamic dispatch
157    socket: Option<Box<dyn ReadWrite>>,
158    /// Unique label identifying this connection
159    label: String,
160    /// Cached device UDID for convenience in higher-level protocols
161    udid: Option<String>,
162}
163
164impl Idevice {
165    /// Creates a new device connection handle
166    ///
167    /// # Arguments
168    /// * `socket` - The established connection socket
169    /// * `label` - Unique identifier for this connection
170    pub fn new(socket: Box<dyn ReadWrite>, label: impl Into<String>) -> Self {
171        Self {
172            socket: Some(socket),
173            label: label.into(),
174            udid: None,
175        }
176    }
177
178    pub fn get_socket(self) -> Option<Box<dyn ReadWrite>> {
179        self.socket
180    }
181
182    /// Sets cached UDID
183    pub fn set_udid(&mut self, udid: impl Into<String>) {
184        self.udid = Some(udid.into());
185    }
186
187    /// Returns cached UDID if available
188    pub fn udid(&self) -> Option<&str> {
189        self.udid.as_deref()
190    }
191
192    /// Queries the device type
193    ///
194    /// Sends a QueryType request and parses the response
195    ///
196    /// # Returns
197    /// The device type string on success
198    ///
199    /// # Errors
200    /// Returns `IdeviceError` if communication fails or response is invalid
201    pub async fn get_type(&mut self) -> Result<String, IdeviceError> {
202        let req = plist!({
203            "Label": self.label.clone(),
204            "Request": "QueryType",
205        });
206        self.send_plist(req).await?;
207
208        let message: plist::Dictionary = self.read_plist().await?;
209        match message.get("Type") {
210            Some(m) => Ok(plist::from_value(m)?),
211            None => Err(IdeviceError::UnexpectedResponse),
212        }
213    }
214
215    /// Performs RSD (Remote Service Discovery) check-in procedure
216    ///
217    /// Establishes the basic service connection protocol
218    ///
219    /// # Errors
220    /// Returns `IdeviceError` if the protocol sequence isn't followed correctly
221    pub async fn rsd_checkin(&mut self) -> Result<(), IdeviceError> {
222        let req = plist!({
223            "Label": self.label.clone(),
224            "ProtocolVersion": "2",
225            "Request": "RSDCheckin",
226        });
227
228        self.send_plist(req).await?;
229        let res = self.read_plist().await?;
230        match res.get("Request").and_then(|x| x.as_string()) {
231            Some(r) => {
232                if r != "RSDCheckin" {
233                    return Err(IdeviceError::UnexpectedResponse);
234                }
235            }
236            None => return Err(IdeviceError::UnexpectedResponse),
237        }
238
239        let res = self.read_plist().await?;
240        match res.get("Request").and_then(|x| x.as_string()) {
241            Some(r) => {
242                if r != "StartService" {
243                    return Err(IdeviceError::UnexpectedResponse);
244                }
245            }
246            None => return Err(IdeviceError::UnexpectedResponse),
247        }
248
249        Ok(())
250    }
251
252    /// Sends a plist-formatted message to the device
253    ///
254    /// # Arguments
255    /// * `message` - The plist value to send
256    ///
257    /// # Errors
258    /// Returns `IdeviceError` if serialization or transmission fails
259    async fn send_plist(&mut self, message: plist::Value) -> Result<(), IdeviceError> {
260        if let Some(socket) = &mut self.socket {
261            debug!("Sending plist: {}", pretty_print_plist(&message));
262
263            let buf = Vec::new();
264            let mut writer = BufWriter::new(buf);
265            message.to_writer_xml(&mut writer)?;
266            let message = writer.into_inner().unwrap();
267            let message = String::from_utf8(message)?;
268            let len = message.len() as u32;
269            socket.write_all(&len.to_be_bytes()).await?;
270            socket.write_all(message.as_bytes()).await?;
271            socket.flush().await?;
272            Ok(())
273        } else {
274            Err(IdeviceError::NoEstablishedConnection)
275        }
276    }
277
278    /// Sends a binary plist-formatted message to the device
279    ///
280    /// # Arguments
281    /// * `message` - The plist value to send
282    ///
283    /// # Errors
284    /// Returns `IdeviceError` if serialization or transmission fails
285    async fn send_bplist(&mut self, message: plist::Value) -> Result<(), IdeviceError> {
286        if let Some(socket) = &mut self.socket {
287            debug!("Sending plist: {}", pretty_print_plist(&message));
288
289            let buf = Vec::new();
290            let mut writer = BufWriter::new(buf);
291            message.to_writer_binary(&mut writer)?;
292            let message = writer.into_inner().unwrap();
293            let len = message.len() as u32;
294            socket.write_all(&len.to_be_bytes()).await?;
295            socket.write_all(&message).await?;
296            socket.flush().await?;
297            Ok(())
298        } else {
299            Err(IdeviceError::NoEstablishedConnection)
300        }
301    }
302
303    /// Sends raw binary data to the device
304    ///
305    /// # Arguments
306    /// * `message` - The bytes to send
307    ///
308    /// # Errors
309    /// Returns `IdeviceError` if transmission fails
310    pub async fn send_raw(&mut self, message: &[u8]) -> Result<(), IdeviceError> {
311        self.send_raw_with_progress(message, |_| async {}, ()).await
312    }
313
314    /// Sends raw binary data via vectored I/O
315    ///
316    /// # Arguments
317    /// * `bufs` - The buffers to send
318    ///
319    /// # Errors
320    /// Returns `IdeviceError` if transmission fails
321    pub async fn send_raw_vectored(
322        &mut self,
323        bufs: &[std::io::IoSlice<'_>],
324    ) -> Result<(), IdeviceError> {
325        if let Some(socket) = &mut self.socket {
326            let mut curr_idx = 0;
327            let mut curr_offset = 0;
328
329            while curr_idx < bufs.len() {
330                let mut iovec = Vec::new();
331                let mut accumulated_len = 0;
332                let max_chunk = 1024 * 64;
333
334                // Add partial first slice
335                let first_avail = bufs[curr_idx].len() - curr_offset;
336                let to_take_first = std::cmp::min(first_avail, max_chunk);
337                iovec.push(std::io::IoSlice::new(
338                    &bufs[curr_idx][curr_offset..curr_offset + to_take_first],
339                ));
340                accumulated_len += to_take_first;
341
342                // Add others up to max_chunk
343                let mut temp_idx = curr_idx + 1;
344                while temp_idx < bufs.len() && accumulated_len < max_chunk {
345                    let needed = max_chunk - accumulated_len;
346                    let avail = bufs[temp_idx].len();
347                    let take = std::cmp::min(avail, needed);
348                    iovec.push(std::io::IoSlice::new(&bufs[temp_idx][..take]));
349                    accumulated_len += take;
350                    temp_idx += 1;
351                }
352
353                let n = socket.write_vectored(&iovec).await?;
354                if n == 0 {
355                    return Err(io::Error::new(
356                        io::ErrorKind::WriteZero,
357                        "failed to write whole buffer",
358                    )
359                    .into());
360                }
361
362                // Advance cursor by n
363                let mut advanced = n;
364                while advanced > 0 && curr_idx < bufs.len() {
365                    let available = bufs[curr_idx].len() - curr_offset;
366                    if advanced < available {
367                        curr_offset += advanced;
368                        advanced = 0;
369                    } else {
370                        advanced -= available;
371                        curr_idx += 1;
372                        curr_offset = 0;
373                    }
374                }
375            }
376            socket.flush().await?;
377            Ok(())
378        } else {
379            Err(IdeviceError::NoEstablishedConnection)
380        }
381    }
382
383    /// Sends raw binary data with progress callbacks
384    ///
385    /// # Arguments
386    /// * `message` - The bytes to send
387    /// * `callback` - Progress callback invoked after each chunk
388    /// * `state` - Arbitrary state passed to callback
389    ///
390    /// # Type Parameters
391    /// * `Fut` - Future type returned by callback
392    /// * `S` - Type of state passed to callback
393    ///
394    /// # Errors
395    /// Returns `IdeviceError` if transmission fails
396    pub async fn send_raw_with_progress<Fut, S>(
397        &mut self,
398        message: &[u8],
399        callback: impl Fn(((usize, usize), S)) -> Fut,
400        state: S,
401    ) -> Result<(), IdeviceError>
402    where
403        Fut: std::future::Future<Output = ()>,
404        S: Clone,
405    {
406        if let Some(socket) = &mut self.socket {
407            let message_parts = message.chunks(1024 * 64);
408            let part_len = message_parts.len() - 1;
409
410            for (i, part) in message_parts.enumerate() {
411                trace!("Writing {i}/{part_len}");
412                socket.write_all(part).await?;
413                callback(((i, part_len), state.clone())).await;
414            }
415            socket.flush().await?;
416            Ok(())
417        } else {
418            Err(IdeviceError::NoEstablishedConnection)
419        }
420    }
421
422    /// Reads exactly `len` bytes from the device
423    ///
424    /// # Arguments
425    /// * `len` - Exact number of bytes to read
426    ///
427    /// # Returns
428    /// The received bytes
429    ///
430    /// # Errors
431    /// Returns `IdeviceError` if reading fails or connection is closed prematurely
432    pub async fn read_raw(&mut self, len: usize) -> Result<Vec<u8>, IdeviceError> {
433        if let Some(socket) = &mut self.socket {
434            let mut buf = vec![0; len];
435            socket.read_exact(&mut buf).await?;
436            Ok(buf)
437        } else {
438            Err(IdeviceError::NoEstablishedConnection)
439        }
440    }
441
442    /// Reads up to `max_size` bytes from the device
443    ///
444    /// # Arguments
445    /// * `max_size` - Maximum number of bytes to read
446    ///
447    /// # Returns
448    /// The received bytes (may be shorter than max_size)
449    ///
450    /// # Errors
451    /// Returns `IdeviceError` if reading fails
452    pub async fn read_any(&mut self, max_size: u32) -> Result<Vec<u8>, IdeviceError> {
453        if let Some(socket) = &mut self.socket {
454            let mut buf = vec![0; max_size as usize];
455            let len = socket.read(&mut buf).await?;
456            Ok(buf[..len].to_vec())
457        } else {
458            Err(IdeviceError::NoEstablishedConnection)
459        }
460    }
461
462    /// Reads a plist-formatted message from the device
463    ///
464    /// # Returns
465    /// The parsed plist dictionary
466    ///
467    /// # Errors
468    /// Returns `IdeviceError` if reading, parsing fails, or device reports an error
469    async fn read_plist(&mut self) -> Result<plist::Dictionary, IdeviceError> {
470        let res = self.read_plist_value().await?;
471        let res: plist::Dictionary = plist::from_value(&res)?;
472        debug!("Received plist: {}", pretty_print_dictionary(&res));
473
474        if let Some(e) = res.get("Error") {
475            let e = match e {
476                plist::Value::String(e) => e.to_string(),
477                plist::Value::Integer(e) => {
478                    if let Some(error_string) = res.get("ErrorString").and_then(|x| x.as_string()) {
479                        error_string.to_string()
480                    } else {
481                        e.to_string()
482                    }
483                }
484                _ => {
485                    tracing::error!("Error is not a string or integer from read_plist: {e:?}");
486                    return Err(IdeviceError::UnexpectedResponse);
487                }
488            };
489            if let Some(e) = IdeviceError::from_device_error_type(e.as_str(), &res) {
490                return Err(e);
491            } else {
492                let msg =
493                    if let Some(desc) = res.get("ErrorDescription").and_then(|x| x.as_string()) {
494                        format!("{} ({})", e, desc)
495                    } else {
496                        e
497                    };
498                return Err(IdeviceError::UnknownErrorType(msg));
499            }
500        }
501        Ok(res)
502    }
503
504    async fn read_plist_value(&mut self) -> Result<plist::Value, IdeviceError> {
505        if let Some(socket) = &mut self.socket {
506            debug!("Reading response size");
507            let mut buf = [0u8; 4];
508            socket.read_exact(&mut buf).await?;
509            let len = u32::from_be_bytes(buf);
510            let mut buf = vec![0; len as usize];
511            socket.read_exact(&mut buf).await?;
512            let res: plist::Value = plist::from_bytes(&buf)?;
513            Ok(res)
514        } else {
515            Err(IdeviceError::NoEstablishedConnection)
516        }
517    }
518
519    #[cfg(feature = "syslog_relay")]
520    async fn read_until_delim(
521        &mut self,
522        delimiter: &[u8],
523    ) -> Result<Option<bytes::BytesMut>, IdeviceError> {
524        if let Some(socket) = &mut self.socket {
525            let mut buffer = bytes::BytesMut::with_capacity(1024);
526            let mut temp = [0u8; 1024];
527
528            loop {
529                let n = socket.read(&mut temp).await?;
530                if n == 0 {
531                    if buffer.is_empty() {
532                        return Ok(None); // EOF and no data
533                    } else {
534                        return Ok(Some(buffer)); // EOF but return partial data
535                    }
536                }
537
538                buffer.extend_from_slice(&temp[..n]);
539
540                if let Some(pos) = buffer.windows(delimiter.len()).position(|w| w == delimiter) {
541                    let mut line = buffer.split_to(pos + delimiter.len());
542                    line.truncate(line.len() - delimiter.len()); // remove delimiter
543                    return Ok(Some(line));
544                }
545            }
546        } else {
547            Err(IdeviceError::NoEstablishedConnection)
548        }
549    }
550
551    /// Upgrades the connection to TLS using device pairing credentials
552    ///
553    /// # Arguments
554    /// * `pairing_file` - Contains the device's identity and certificates
555    ///
556    /// # Errors
557    /// Returns `IdeviceError` if TLS handshake fails or credentials are invalid
558    pub async fn start_session(
559        &mut self,
560        pairing_file: &pairing_file::PairingFile,
561        legacy: bool,
562    ) -> Result<(), IdeviceError> {
563        #[cfg(feature = "rustls")]
564        {
565            if legacy {
566                tracing::warn!(
567                    "Compiled with rustls, but connecting to legacy device! rustls does not support old SSL, this will fail."
568                );
569            }
570
571            if CryptoProvider::get_default().is_none() {
572                // rust-analyzer will choke on this block, don't worry about it
573                let crypto_provider: CryptoProvider = {
574                    #[cfg(all(feature = "ring", not(feature = "aws-lc")))]
575                    {
576                        debug!("Using ring crypto backend");
577                        rustls::crypto::ring::default_provider()
578                    }
579
580                    #[cfg(all(feature = "aws-lc", not(feature = "ring")))]
581                    {
582                        debug!("Using aws-lc crypto backend");
583                        rustls::crypto::aws_lc_rs::default_provider()
584                    }
585
586                    #[cfg(not(any(feature = "ring", feature = "aws-lc")))]
587                    {
588                        compile_error!(
589                            "No crypto backend was selected! Specify an idevice feature for a crypto backend"
590                        );
591                    }
592
593                    #[cfg(all(feature = "ring", feature = "aws-lc"))]
594                    {
595                        // We can't throw a compile error because it breaks rust-analyzer.
596                        // My sanity while debugging the workspace crates are more important.
597
598                        debug!("Using ring crypto backend, because both were passed");
599                        tracing::warn!(
600                            "Both ring && aws-lc are selected as idevice crypto backends!"
601                        );
602                        rustls::crypto::ring::default_provider()
603                    }
604                };
605
606                if let Err(e) = CryptoProvider::install_default(crypto_provider) {
607                    // For whatever reason, getting the default provider will return None on iOS at
608                    // random. Installing the default provider a second time will return an error, so
609                    // we will log it but not propogate it. An issue should be opened with rustls.
610                    tracing::error!("Failed to set crypto provider: {e:?}");
611                }
612            }
613            let config = sni::create_client_config(pairing_file)?;
614            let connector = tokio_rustls::TlsConnector::from(Arc::new(config));
615
616            let socket = self.socket.take().unwrap();
617            let socket = connector
618                .connect(ServerName::try_from("Device").unwrap(), socket)
619                .await?;
620
621            self.socket = Some(Box::new(socket));
622
623            Ok(())
624        }
625        #[cfg(all(feature = "openssl", not(feature = "rustls")))]
626        {
627            let mut connector =
628                openssl::ssl::SslConnector::builder(openssl::ssl::SslMethod::tls())?;
629            if legacy {
630                connector.set_min_proto_version(Some(openssl::ssl::SslVersion::SSL3))?;
631                connector.set_max_proto_version(Some(openssl::ssl::SslVersion::TLS1))?;
632                connector.set_cipher_list("ALL:!aNULL:!eNULL:@SECLEVEL=0")?;
633                connector.set_options(openssl::ssl::SslOptions::ALLOW_UNSAFE_LEGACY_RENEGOTIATION);
634            }
635
636            let mut connector = connector.build().configure()?.into_ssl("ur mom")?;
637
638            connector.set_certificate(&pairing_file.host_certificate)?;
639            connector.set_private_key(&pairing_file.host_private_key)?;
640            connector.set_verify(openssl::ssl::SslVerifyMode::empty());
641            let socket = self.socket.take().unwrap();
642            let mut ssl_stream = tokio_openssl::SslStream::new(connector, socket)?;
643            std::pin::Pin::new(&mut ssl_stream).connect().await?;
644            self.socket = Some(Box::new(ssl_stream));
645
646            Ok(())
647        }
648    }
649}
650
651/// Comprehensive error type for all device communication failures
652#[derive(Error, Debug)]
653#[repr(i32)]
654#[non_exhaustive]
655pub enum IdeviceError {
656    #[error("device socket io failed")]
657    Socket(#[from] io::Error) = -1,
658    #[cfg(feature = "rustls")]
659    #[error("PEM parse failed")]
660    PemParseFailed(#[from] rustls::pki_types::pem::Error) = -2,
661
662    #[cfg(feature = "rustls")]
663    #[error("TLS error")]
664    Rustls(#[from] rustls::Error) = -3,
665    #[cfg(all(feature = "openssl", not(feature = "rustls")))]
666    #[error("TLS error")]
667    Rustls(#[from] openssl::ssl::Error) = -3,
668
669    #[cfg(feature = "rustls")]
670    #[error("TLS verifiction build failed")]
671    TlsBuilderFailed(#[from] rustls::server::VerifierBuilderError) = -4,
672    #[cfg(all(feature = "openssl", not(feature = "rustls")))]
673    #[error("TLS verifiction build failed")]
674    TlsBuilderFailed(#[from] openssl::error::ErrorStack) = -4,
675
676    #[error("io on plist")]
677    Plist(#[from] plist::Error) = -5,
678    #[error("can't convert bytes to utf8")]
679    Utf8(#[from] std::string::FromUtf8Error) = -6,
680    #[error("unexpected response from device")]
681    UnexpectedResponse = -7,
682    #[error("this request was prohibited")]
683    GetProhibited = -8,
684    #[error("no SSL session is active")]
685    SessionInactive = -9,
686    #[error("device does not have pairing file")]
687    InvalidHostID = -10,
688    #[error("no established connection")]
689    NoEstablishedConnection = -11,
690    #[error("device went to sleep")]
691    HeartbeatSleepyTime = -12,
692    #[error("heartbeat timeout")]
693    HeartbeatTimeout = -13,
694    #[error("not found")]
695    NotFound = -14,
696    #[error("service not found")]
697    ServiceNotFound = -15,
698    #[error("CDTunnel packet too short")]
699    CdtunnelPacketTooShort = -16,
700    #[error("CDTunnel packet invalid magic")]
701    CdtunnelPacketInvalidMagic = -17,
702    #[error("Proclaimed packet size does not match actual size")]
703    PacketSizeMismatch = -18,
704
705    #[cfg(feature = "core_device_proxy")]
706    #[error("JSON serialization failed")]
707    Json(#[from] serde_json::Error) = -19,
708
709    #[error("device not found")]
710    DeviceNotFound = -20,
711
712    #[error("device lockded")]
713    DeviceLocked = -21,
714
715    #[error("device refused connection")]
716    UsbConnectionRefused = -22,
717    #[error("bad command")]
718    UsbBadCommand = -23,
719    #[error("bad device")]
720    UsbBadDevice = -24,
721    #[error("usb bad version")]
722    UsbBadVersion = -25,
723
724    #[error("bad build manifest")]
725    BadBuildManifest = -26,
726    #[error("image not mounted")]
727    ImageNotMounted = -27,
728
729    #[cfg(feature = "pair")]
730    #[error("pairing trust dialog pending")]
731    PairingDialogResponsePending = -28,
732
733    #[cfg(feature = "pair")]
734    #[error("user denied pairing trust")]
735    UserDeniedPairing = -29,
736
737    #[cfg(feature = "pair")]
738    #[error("device is locked")]
739    PasswordProtected = -30,
740
741    #[cfg(feature = "misagent")]
742    #[error("misagent operation failed")]
743    MisagentFailure = -31,
744
745    #[cfg(feature = "installation_proxy")]
746    #[error("installation proxy operation failed")]
747    InstallationProxyOperationFailed(String) = -32,
748
749    #[cfg(feature = "afc")]
750    #[error("afc error: {0}")]
751    Afc(#[from] afc::errors::AfcError) = -33,
752
753    #[cfg(feature = "afc")]
754    #[error("unknown afc opcode")]
755    UnknownAfcOpcode = -34,
756
757    #[cfg(feature = "afc")]
758    #[error("invalid afc magic")]
759    InvalidAfcMagic = -35,
760
761    #[cfg(feature = "afc")]
762    #[error("missing file attribute")]
763    AfcMissingAttribute = -36,
764
765    #[cfg(feature = "crashreportcopymobile")]
766    #[error("crash report mover sent the wrong response")]
767    CrashReportMoverBadResponse(Vec<u8>) = -37,
768
769    #[cfg(any(feature = "tss", feature = "tunneld"))]
770    #[error("http reqwest error")]
771    Reqwest(#[from] reqwest::Error) = -38,
772
773    #[error("internal error")]
774    InternalError(String) = -39,
775
776    #[cfg(feature = "xpc")]
777    #[error("unknown http frame type")]
778    UnknownFrame(u8) = -40,
779
780    #[cfg(feature = "xpc")]
781    #[error("unknown http setting type")]
782    UnknownHttpSetting(u16) = -41,
783
784    #[cfg(feature = "xpc")]
785    #[error("Unintialized stream ID")]
786    UninitializedStreamId = -42,
787
788    #[cfg(feature = "xpc")]
789    #[error("unknown XPC type")]
790    UnknownXpcType(u32) = -43,
791
792    #[cfg(feature = "xpc")]
793    #[error("malformed XPC message")]
794    MalformedXpc = -44,
795
796    #[cfg(feature = "xpc")]
797    #[error("invalid XPC magic")]
798    InvalidXpcMagic = -45,
799
800    #[cfg(feature = "xpc")]
801    #[error("unexpected XPC version")]
802    UnexpectedXpcVersion = -46,
803
804    #[cfg(feature = "xpc")]
805    #[error("invalid C string")]
806    InvalidCString = -47,
807
808    #[cfg(feature = "xpc")]
809    #[error("stream reset")]
810    HttpStreamReset = -48,
811
812    #[cfg(feature = "xpc")]
813    #[error("go away packet received")]
814    HttpGoAway(String) = -49,
815
816    #[cfg(feature = "dvt")]
817    #[error("NSKeyedArchive error")]
818    NsKeyedArchiveError(#[from] ns_keyed_archive::ConverterError) = -50,
819
820    #[cfg(feature = "dvt")]
821    #[error("Unknown aux value type")]
822    UnknownAuxValueType(u32) = -51,
823
824    #[cfg(feature = "dvt")]
825    #[error("unknown channel")]
826    UnknownChannel(u32) = -52,
827
828    #[error("cannot parse string as IpAddr")]
829    AddrParseError(#[from] std::net::AddrParseError) = -53,
830
831    #[cfg(feature = "dvt")]
832    #[error("disable memory limit failed")]
833    DisableMemoryLimitFailed = -54,
834
835    #[error("not enough bytes, expected {1}, got {0}")]
836    NotEnoughBytes(usize, usize) = -55,
837
838    #[error("failed to parse bytes as valid utf8")]
839    Utf8Error = -56,
840
841    #[cfg(any(
842        feature = "debug_proxy",
843        all(feature = "afc", feature = "installation_proxy")
844    ))]
845    #[error("invalid argument passed")]
846    InvalidArgument = -57,
847
848    #[error("unknown error `{0}` returned from device")]
849    UnknownErrorType(String) = -59,
850
851    #[error("invalid arguments were passed")]
852    FfiInvalidArg = -60,
853    #[error("invalid string was passed")]
854    FfiInvalidString = -61,
855    #[error("buffer passed is too small - needs {0}, got {1}")]
856    FfiBufferTooSmall(usize, usize) = -62,
857    #[error("unsupported watch key")]
858    UnsupportedWatchKey = -63,
859    #[error("malformed command")]
860    MalformedCommand = -64,
861    #[error("integer overflow")]
862    IntegerOverflow = -65,
863    #[error("canceled by user")]
864    CanceledByUser = -66,
865
866    #[cfg(feature = "installation_proxy")]
867    #[error("malformed package archive: {0}")]
868    MalformedPackageArchive(#[from] async_zip::error::ZipError) = -67,
869
870    #[error("Developer mode is not enabled")]
871    DeveloperModeNotEnabled = -68,
872
873    #[error("Unknown TLV {0}")]
874    UnknownTlv(u8) = -69,
875    #[error("Malformed TLV")]
876    MalformedTlv = -70,
877    #[error("Pairing rejected: {0}")]
878    PairingRejected(String) = -71,
879    #[cfg(feature = "remote_pairing")]
880    #[error("Base64 decode error")]
881    Base64DecodeError(#[from] base64::DecodeError) = -72,
882    #[error("Pair verified failed")]
883    PairVerifyFailed = -73,
884    #[error("SRP auth failed")]
885    SrpAuthFailed = -74,
886    #[cfg(feature = "remote_pairing")]
887    #[error("Chacha encryption error")]
888    ChachaEncryption(chacha20poly1305::Error) = -75,
889    #[cfg(feature = "notification_proxy")]
890    #[error("notification proxy died")]
891    NotificationProxyDeath = -76,
892
893    #[cfg(feature = "installation_proxy")]
894    #[error("Application verification failed: {0}")]
895    ApplicationVerificationFailed(String) = -78,
896}
897
898impl IdeviceError {
899    /// Converts a device-reported error string to a typed error
900    ///
901    /// # Arguments
902    /// * `e` - The error string from device
903    /// * `context` - Full plist context containing additional error details
904    ///
905    /// # Returns
906    /// Some(IdeviceError) if the string maps to a known error type, None otherwise
907    fn from_device_error_type(e: &str, context: &plist::Dictionary) -> Option<Self> {
908        if e.contains("NSDebugDescription=Canceled by user.") {
909            return Some(Self::CanceledByUser);
910        } else if e.contains("Developer mode is not enabled.") {
911            return Some(Self::DeveloperModeNotEnabled);
912        }
913        match e {
914            "GetProhibited" => Some(Self::GetProhibited),
915            "InvalidHostID" => Some(Self::InvalidHostID),
916            "SessionInactive" => Some(Self::SessionInactive),
917            "DeviceLocked" => Some(Self::DeviceLocked),
918            #[cfg(feature = "pair")]
919            "PairingDialogResponsePending" => Some(Self::PairingDialogResponsePending),
920            #[cfg(feature = "pair")]
921            "UserDeniedPairing" => Some(Self::UserDeniedPairing),
922            #[cfg(feature = "pair")]
923            "PasswordProtected" => Some(Self::PasswordProtected),
924            "UnsupportedWatchKey" => Some(Self::UnsupportedWatchKey),
925            "MalformedCommand" => Some(Self::MalformedCommand),
926            "InternalError" => {
927                let detailed_error = context
928                    .get("DetailedError")
929                    .and_then(|d| d.as_string())
930                    .unwrap_or("No context")
931                    .to_string();
932
933                if detailed_error.contains("There is no matching entry in the device map for") {
934                    Some(Self::ImageNotMounted)
935                } else {
936                    Some(Self::InternalError(detailed_error))
937                }
938            }
939            #[cfg(feature = "installation_proxy")]
940            "ApplicationVerificationFailed" => {
941                let msg = context
942                    .get("ErrorDescription")
943                    .and_then(|x| x.as_string())
944                    .unwrap_or("No context")
945                    .to_string();
946                Some(Self::ApplicationVerificationFailed(msg))
947            }
948            _ => None,
949        }
950    }
951
952    pub fn code(&self) -> i32 {
953        match self {
954            IdeviceError::Socket(_) => -1,
955            #[cfg(feature = "rustls")]
956            IdeviceError::PemParseFailed(_) => -2,
957            IdeviceError::Rustls(_) => -3,
958            IdeviceError::TlsBuilderFailed(_) => -4,
959            IdeviceError::Plist(_) => -5,
960            IdeviceError::Utf8(_) => -6,
961            IdeviceError::UnexpectedResponse => -7,
962            IdeviceError::GetProhibited => -8,
963            IdeviceError::SessionInactive => -9,
964            IdeviceError::InvalidHostID => -10,
965            IdeviceError::NoEstablishedConnection => -11,
966            IdeviceError::HeartbeatSleepyTime => -12,
967            IdeviceError::HeartbeatTimeout => -13,
968            IdeviceError::NotFound => -14,
969            IdeviceError::ServiceNotFound => -15,
970            IdeviceError::CdtunnelPacketTooShort => -16,
971            IdeviceError::CdtunnelPacketInvalidMagic => -17,
972            IdeviceError::PacketSizeMismatch => -18,
973
974            #[cfg(feature = "core_device_proxy")]
975            IdeviceError::Json(_) => -19,
976
977            IdeviceError::DeviceNotFound => -20,
978            IdeviceError::DeviceLocked => -21,
979            IdeviceError::UsbConnectionRefused => -22,
980            IdeviceError::UsbBadCommand => -23,
981            IdeviceError::UsbBadDevice => -24,
982            IdeviceError::UsbBadVersion => -25,
983            IdeviceError::BadBuildManifest => -26,
984            IdeviceError::ImageNotMounted => -27,
985
986            #[cfg(feature = "pair")]
987            IdeviceError::PairingDialogResponsePending => -28,
988            #[cfg(feature = "pair")]
989            IdeviceError::UserDeniedPairing => -29,
990            #[cfg(feature = "pair")]
991            IdeviceError::PasswordProtected => -30,
992
993            #[cfg(feature = "misagent")]
994            IdeviceError::MisagentFailure => -31,
995
996            #[cfg(feature = "installation_proxy")]
997            IdeviceError::InstallationProxyOperationFailed(_) => -32,
998
999            #[cfg(feature = "afc")]
1000            IdeviceError::Afc(_) => -33,
1001            #[cfg(feature = "afc")]
1002            IdeviceError::UnknownAfcOpcode => -34,
1003            #[cfg(feature = "afc")]
1004            IdeviceError::InvalidAfcMagic => -35,
1005            #[cfg(feature = "afc")]
1006            IdeviceError::AfcMissingAttribute => -36,
1007
1008            #[cfg(feature = "crashreportcopymobile")]
1009            IdeviceError::CrashReportMoverBadResponse(_) => -37,
1010
1011            #[cfg(any(feature = "tss", feature = "tunneld"))]
1012            IdeviceError::Reqwest(_) => -38,
1013
1014            IdeviceError::InternalError(_) => -39,
1015
1016            #[cfg(feature = "xpc")]
1017            IdeviceError::UnknownFrame(_) => -40,
1018            #[cfg(feature = "xpc")]
1019            IdeviceError::UnknownHttpSetting(_) => -41,
1020            #[cfg(feature = "xpc")]
1021            IdeviceError::UninitializedStreamId => -42,
1022            #[cfg(feature = "xpc")]
1023            IdeviceError::UnknownXpcType(_) => -43,
1024            #[cfg(feature = "xpc")]
1025            IdeviceError::MalformedXpc => -44,
1026            #[cfg(feature = "xpc")]
1027            IdeviceError::InvalidXpcMagic => -45,
1028            #[cfg(feature = "xpc")]
1029            IdeviceError::UnexpectedXpcVersion => -46,
1030            #[cfg(feature = "xpc")]
1031            IdeviceError::InvalidCString => -47,
1032            #[cfg(feature = "xpc")]
1033            IdeviceError::HttpStreamReset => -48,
1034            #[cfg(feature = "xpc")]
1035            IdeviceError::HttpGoAway(_) => -49,
1036
1037            #[cfg(feature = "dvt")]
1038            IdeviceError::NsKeyedArchiveError(_) => -50,
1039            #[cfg(feature = "dvt")]
1040            IdeviceError::UnknownAuxValueType(_) => -51,
1041            #[cfg(feature = "dvt")]
1042            IdeviceError::UnknownChannel(_) => -52,
1043
1044            IdeviceError::AddrParseError(_) => -53,
1045
1046            #[cfg(feature = "dvt")]
1047            IdeviceError::DisableMemoryLimitFailed => -54,
1048
1049            IdeviceError::NotEnoughBytes(_, _) => -55,
1050            IdeviceError::Utf8Error => -56,
1051
1052            #[cfg(any(
1053                feature = "debug_proxy",
1054                all(feature = "afc", feature = "installation_proxy")
1055            ))]
1056            IdeviceError::InvalidArgument => -57,
1057
1058            IdeviceError::UnknownErrorType(_) => -59,
1059            IdeviceError::FfiInvalidArg => -60,
1060            IdeviceError::FfiInvalidString => -61,
1061            IdeviceError::FfiBufferTooSmall(_, _) => -62,
1062            IdeviceError::UnsupportedWatchKey => -63,
1063            IdeviceError::MalformedCommand => -64,
1064            IdeviceError::IntegerOverflow => -65,
1065            IdeviceError::CanceledByUser => -66,
1066
1067            #[cfg(feature = "installation_proxy")]
1068            IdeviceError::MalformedPackageArchive(_) => -67,
1069            IdeviceError::DeveloperModeNotEnabled => -68,
1070            IdeviceError::UnknownTlv(_) => -69,
1071            IdeviceError::MalformedTlv => -70,
1072            IdeviceError::PairingRejected(_) => -71,
1073            #[cfg(feature = "remote_pairing")]
1074            IdeviceError::Base64DecodeError(_) => -72,
1075            IdeviceError::PairVerifyFailed => -73,
1076            IdeviceError::SrpAuthFailed => -74,
1077            #[cfg(feature = "remote_pairing")]
1078            IdeviceError::ChachaEncryption(_) => -75,
1079
1080            #[cfg(feature = "notification_proxy")]
1081            IdeviceError::NotificationProxyDeath => -76,
1082
1083            #[cfg(feature = "installation_proxy")]
1084            IdeviceError::ApplicationVerificationFailed(_) => -78,
1085        }
1086    }
1087}