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