mirror of
https://github.com/nab138/isideload.git
synced 2026-03-02 06:26:16 +01:00
start refactoring sideloader
This commit is contained in:
98
Cargo.lock
generated
98
Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -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("<Unnamed>"),
|
||||
team.team_id
|
||||
);
|
||||
}
|
||||
let mut input = String::new();
|
||||
std::io::stdin().read_line(&mut input).unwrap();
|
||||
let selection = input.trim().parse::<usize>().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("<Unnamed>"),
|
||||
team.team_id
|
||||
);
|
||||
}
|
||||
let mut input = String::new();
|
||||
std::io::stdin().read_line(&mut input).unwrap();
|
||||
let selection = input.trim().parse::<usize>().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),
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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<dyn SideloadingStorage>,
|
||||
}
|
||||
|
||||
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<dyn SideloadingStorage>) -> Self {
|
||||
self.storage = storage;
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -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};
|
||||
|
||||
66
isideload/src/sideload/sideloader.rs
Normal file
66
isideload/src/sideload/sideloader.rs
Normal file
@@ -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<dyn SideloadingStorage>,
|
||||
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<DeveloperTeam, Report> {
|
||||
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))?
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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<Option<String>, 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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Option<String>, 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<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
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<Option<String>, 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(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user