From 6d0f644e9368c88ad60a1334b6320c32ccd4ce12 Mon Sep 17 00:00:00 2001 From: nab138 Date: Fri, 6 Feb 2026 08:30:59 -0500 Subject: [PATCH] start refactoring sideloader --- Cargo.lock | 98 ++++++++++++++++++- examples/minimal/src/main.rs | 41 ++++---- isideload/Cargo.toml | 2 +- .../src/sideload/{config.rs => builder.rs} | 19 ++-- isideload/src/sideload/mod.rs | 53 +--------- isideload/src/sideload/sideloader.rs | 66 +++++++++++++ isideload/src/util/keyring_storage.rs | 36 +++++++ isideload/src/util/storage.rs | 47 +++++++++ 8 files changed, 282 insertions(+), 80 deletions(-) rename isideload/src/sideload/{config.rs => builder.rs} (68%) create mode 100644 isideload/src/sideload/sideloader.rs diff --git a/Cargo.lock b/Cargo.lock index 1d69ccd..31e7245 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -157,6 +157,12 @@ version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.11.1" @@ -370,6 +376,27 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" +[[package]] +name = "dbus" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b3aa68d7e7abee336255bd7248ea965cc393f3e70411135a6f6a4b651345d4" +dependencies = [ + "libc", + "libdbus-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "dbus-secret-service" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "708b509edf7889e53d7efb0ffadd994cc6c2345ccb62f55cfd6b0682165e4fa6" +dependencies = [ + "dbus", + "zeroize", +] + [[package]] name = "deranged" version = "0.5.5" @@ -1040,7 +1067,13 @@ version = "3.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c" dependencies = [ + "byteorder", + "dbus-secret-service", + "linux-keyutils", "log", + "security-framework 2.11.1", + "security-framework 3.5.1", + "windows-sys 0.60.2", "zeroize", ] @@ -1062,6 +1095,25 @@ version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +[[package]] +name = "libdbus-sys" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043" +dependencies = [ + "pkg-config", +] + +[[package]] +name = "linux-keyutils" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "761e49ec5fd8a5a463f9b84e877c373d888935b71c6be78f3767fe2ae6bed18e" +dependencies = [ + "bitflags", + "libc", +] + [[package]] name = "litemap" version = "0.8.1" @@ -1190,6 +1242,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + [[package]] name = "plist" version = "1.8.0" @@ -1485,7 +1543,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework", + "security-framework 3.5.1", ] [[package]] @@ -1512,7 +1570,7 @@ dependencies = [ "rustls-native-certs", "rustls-platform-verifier-android", "rustls-webpki", - "security-framework", + "security-framework 3.5.1", "security-framework-sys", "webpki-root-certs", "windows-sys 0.61.2", @@ -1559,6 +1617,19 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + [[package]] name = "security-framework" version = "3.5.1" @@ -2416,6 +2487,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.60.2" @@ -2783,6 +2863,20 @@ name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "zerotrie" diff --git a/examples/minimal/src/main.rs b/examples/minimal/src/main.rs index eb40aee..9430ffc 100644 --- a/examples/minimal/src/main.rs +++ b/examples/minimal/src/main.rs @@ -5,7 +5,7 @@ use isideload::{ anisette::remote_v3::RemoteV3AnisetteProvider, auth::apple_account::AppleAccount, dev::developer_session::DeveloperSession, - sideload::{SideloadConfiguration, TeamSelection, sideload_app}, + sideload::{SideloaderBuilder, TeamSelection}, }; use tracing::Level; @@ -70,27 +70,26 @@ async fn main() { .unwrap() .to_provider(UsbmuxdAddr::from_env_var().unwrap(), "isideload-demo"); - let sideload_config = - SideloadConfiguration::builder().team_selection(TeamSelection::Prompt(|teams| { - println!("Please select a team:"); - for (index, team) in teams.iter().enumerate() { - println!( - "{}: {} ({})", - index + 1, - team.name.as_deref().unwrap_or(""), - team.team_id - ); - } - let mut input = String::new(); - std::io::stdin().read_line(&mut input).unwrap(); - let selection = input.trim().parse::().ok()?; - if selection == 0 || selection > teams.len() { - return None; - } - Some(teams[selection - 1].team_id.clone()) - })); + let builder = SideloaderBuilder::new().team_selection(TeamSelection::Prompt(|teams| { + println!("Please select a team:"); + for (index, team) in teams.iter().enumerate() { + println!( + "{}: {} ({})", + index + 1, + team.name.as_deref().unwrap_or(""), + team.team_id + ); + } + let mut input = String::new(); + std::io::stdin().read_line(&mut input).unwrap(); + let selection = input.trim().parse::().ok()?; + if selection == 0 || selection > teams.len() { + return None; + } + Some(teams[selection - 1].team_id.clone()) + })); - let result = sideload_app(&provider, &mut dev_session, app_path, &sideload_config).await; + // let result = bu(&provider, &mut dev_session, app_path, &sideload_config).await; match result { Ok(_) => println!("App installed successfully"), Err(e) => panic!("Failed to install app: {:?}", e), diff --git a/isideload/Cargo.toml b/isideload/Cargo.toml index 9af6964..6e39db5 100644 --- a/isideload/Cargo.toml +++ b/isideload/Cargo.toml @@ -40,4 +40,4 @@ cbc = { version = "0.2.0-rc.3", features = ["alloc"] } aes = "0.9.0-rc.4" aes-gcm = "0.11.0-rc.3" tokio = "1.49.0" -keyring = { version = "3.6.3", optional = true } +keyring = { version = "3.6.3", features = ["apple-native", "linux-native-sync-persistent", "windows-native"], optional = true } diff --git a/isideload/src/sideload/config.rs b/isideload/src/sideload/builder.rs similarity index 68% rename from isideload/src/sideload/config.rs rename to isideload/src/sideload/builder.rs index a09f075..51a8ea1 100644 --- a/isideload/src/sideload/config.rs +++ b/isideload/src/sideload/builder.rs @@ -1,6 +1,6 @@ use std::fmt::Display; -use crate::dev::teams::DeveloperTeam; +use crate::{dev::teams::DeveloperTeam, util::storage::SideloadingStorage}; /// Configuration for selecting a developer team during sideloading /// @@ -22,20 +22,22 @@ impl Display for TeamSelection { } } -pub struct SideloadConfiguration { +pub struct SideloaderBuilder { pub team_selection: TeamSelection, + pub storage: Box, } -impl Default for SideloadConfiguration { +impl Default for SideloaderBuilder { fn default() -> Self { - SideloadConfiguration { + SideloaderBuilder { team_selection: TeamSelection::First, + storage: Box::new(crate::util::storage::new_storage()), } } } -impl SideloadConfiguration { - pub fn builder() -> Self { +impl SideloaderBuilder { + pub fn new() -> Self { Self::default() } @@ -43,4 +45,9 @@ impl SideloadConfiguration { self.team_selection = selection; self } + + pub fn storage(mut self, storage: Box) -> Self { + self.storage = storage; + self + } } diff --git a/isideload/src/sideload/mod.rs b/isideload/src/sideload/mod.rs index ecf254f..2446950 100644 --- a/isideload/src/sideload/mod.rs +++ b/isideload/src/sideload/mod.rs @@ -1,51 +1,4 @@ -use std::path::PathBuf; - -use idevice::provider::IdeviceProvider; -use rootcause::prelude::*; -use tracing::info; - -use crate::dev::teams::TeamsApi; -use crate::dev::{developer_session::DeveloperSession, devices::DevicesApi}; -use crate::util::device::IdeviceInfo; - +pub mod builder; pub mod certificate; -pub mod config; -pub use config::{SideloadConfiguration, TeamSelection}; - -pub async fn sideload_app( - device_provider: &impl IdeviceProvider, - dev_session: &mut DeveloperSession, - app_path: PathBuf, - config: &SideloadConfiguration, -) -> Result<(), Report> { - let device_info = IdeviceInfo::from_device(device_provider).await?; - let teams = dev_session.list_teams().await?; - let team = match teams.len() { - 0 => { - bail!("No developer teams available") - } - 1 => &teams[0], - _ => { - info!( - "Multiple developer teams found, {} as per configuration", - config.team_selection - ); - match &config.team_selection { - TeamSelection::First => &teams[0], - TeamSelection::Prompt(prompt_fn) => { - let selection = prompt_fn(&teams).ok_or_else(|| report!("No team selected"))?; - teams - .iter() - .find(|t| t.team_id == selection) - .ok_or_else(|| report!("No team found with ID {}", selection))? - } - } - } - }; - - dev_session - .ensure_device_registered(team, &device_info.name, &device_info.udid, None) - .await?; - - Ok(()) -} +pub mod sideloader; +pub use builder::{SideloaderBuilder, TeamSelection}; diff --git a/isideload/src/sideload/sideloader.rs b/isideload/src/sideload/sideloader.rs new file mode 100644 index 0000000..904ce4a --- /dev/null +++ b/isideload/src/sideload/sideloader.rs @@ -0,0 +1,66 @@ +use crate::{ + dev::{ + developer_session::DeveloperSession, + devices::DevicesApi, + teams::{DeveloperTeam, TeamsApi}, + }, + sideload::TeamSelection, + util::{device::IdeviceInfo, storage::SideloadingStorage}, +}; + +use std::path::PathBuf; + +use idevice::provider::IdeviceProvider; +use rootcause::prelude::*; +use tracing::info; + +pub struct Sideloader { + pub team_selection: TeamSelection, + pub storage: Box, + pub dev_session: DeveloperSession, +} + +impl Sideloader { + pub async fn install_app( + &mut self, + device_provider: &impl IdeviceProvider, + app_path: PathBuf, + ) -> Result<(), Report> { + let device_info = IdeviceInfo::from_device(device_provider).await?; + + let team = self.get_team().await?; + + self.dev_session + .ensure_device_registered(&team, &device_info.name, &device_info.udid, None) + .await?; + + Ok(()) + } + + pub async fn get_team(&mut self) -> Result { + let teams = self.dev_session.list_teams().await?; + Ok(match teams.len() { + 0 => { + bail!("No developer teams available") + } + 1 => teams.into_iter().next().unwrap(), + _ => { + info!( + "Multiple developer teams found, {} as per configuration", + self.team_selection + ); + match &self.team_selection { + TeamSelection::First => teams.into_iter().next().unwrap(), + TeamSelection::Prompt(prompt_fn) => { + let selection = + prompt_fn(&teams).ok_or_else(|| report!("No team selected"))?; + teams + .into_iter() + .find(|t| t.team_id == selection) + .ok_or_else(|| report!("No team found with ID {}", selection))? + } + } + } + }) + } +} diff --git a/isideload/src/util/keyring_storage.rs b/isideload/src/util/keyring_storage.rs index e69de29..bc0f495 100644 --- a/isideload/src/util/keyring_storage.rs +++ b/isideload/src/util/keyring_storage.rs @@ -0,0 +1,36 @@ +use crate::util::storage::SideloadingStorage; +use keyring::Entry; +use rootcause::prelude::*; + +pub struct KeyringStorage {} + +impl KeyringStorage { + pub fn new() -> Self { + KeyringStorage {} + } +} + +impl SideloadingStorage for KeyringStorage { + fn store(&self, key: &str, value: &str) -> Result<(), Report> { + Entry::new("isideload", key)?.set_password(value)?; + Ok(()) + } + + fn retrieve(&self, key: &str) -> Result, Report> { + let entry = Entry::new("isideload", key)?; + match entry.get_password() { + Ok(password) => Ok(Some(password)), + Err(keyring::Error::NoEntry) => Ok(None), + Err(e) => Err(e.into()), + } + } + + fn delete(&self, key: &str) -> Result<(), Report> { + let entry = Entry::new("isideload", key)?; + match entry.delete_credential() { + Ok(()) => Ok(()), + Err(keyring::Error::NoEntry) => Ok(()), + Err(e) => Err(e.into()), + } + } +} diff --git a/isideload/src/util/storage.rs b/isideload/src/util/storage.rs index 109d554..9eccab4 100644 --- a/isideload/src/util/storage.rs +++ b/isideload/src/util/storage.rs @@ -1,9 +1,14 @@ +use std::{collections::HashMap, sync::Mutex}; + use base64::prelude::*; use rootcause::prelude::*; pub trait SideloadingStorage: Send + Sync { fn store(&self, key: &str, value: &str) -> Result<(), Report>; fn retrieve(&self, key: &str) -> Result, Report>; + fn delete(&self, key: &str) -> Result<(), Report> { + self.store(key, "") + } fn store_data(&self, key: &str, value: &[u8]) -> Result<(), Report> { let encoded = BASE64_STANDARD.encode(value); @@ -19,3 +24,45 @@ pub trait SideloadingStorage: Send + Sync { } } } + +pub fn new_storage() -> impl SideloadingStorage { + #[cfg(feature = "keyring-storage")] + { + crate::util::keyring_storage::KeyringStorage::new() + } + #[cfg(not(feature = "keyring-storage"))] + { + InMemoryStorage::new() + } +} + +pub struct InMemoryStorage { + storage: Mutex>, +} + +impl InMemoryStorage { + pub fn new() -> Self { + InMemoryStorage { + storage: Mutex::new(HashMap::new()), + } + } +} + +impl SideloadingStorage for InMemoryStorage { + fn store(&self, key: &str, value: &str) -> Result<(), Report> { + let mut storage = self.storage.lock().unwrap(); + storage.insert(key.to_string(), value.to_string()); + Ok(()) + } + + fn retrieve(&self, key: &str) -> Result, Report> { + let storage = self.storage.lock().unwrap(); + Ok(storage.get(key).cloned()) + } + + fn delete(&self, key: &str) -> Result<(), Report> { + let mut storage = self.storage.lock().unwrap(); + storage.remove(key); + Ok(()) + } +}