From 32cc042c0761229157efc0a567fd4bddb2ebf80b Mon Sep 17 00:00:00 2001 From: nab138 Date: Sat, 14 Feb 2026 17:23:52 -0500 Subject: [PATCH] Use storage provider for anisette and provide fs-storage implementation --- examples/minimal/src/main.rs | 4 -- isideload/Cargo.toml | 1 + isideload/src/anisette/remote_v3/mod.rs | 42 ++++++++++++-------- isideload/src/util/fs_storage.rs | 53 +++++++++++++++++++++++++ isideload/src/util/mod.rs | 2 + isideload/src/util/storage.rs | 13 ++++-- 6 files changed, 92 insertions(+), 23 deletions(-) create mode 100644 isideload/src/util/fs_storage.rs diff --git a/examples/minimal/src/main.rs b/examples/minimal/src/main.rs index ec50000..b6138b2 100644 --- a/examples/minimal/src/main.rs +++ b/examples/minimal/src/main.rs @@ -9,7 +9,6 @@ use isideload::{ teams::DeveloperTeam, }, sideload::{SideloaderBuilder, TeamSelection, builder::MaxCertsBehavior}, - util::keyring_storage::KeyringStorage, }; use tracing::Level; @@ -120,9 +119,6 @@ async fn main() { let mut sideloader = SideloaderBuilder::new(dev_session, apple_id.to_string()) .team_selection(TeamSelection::Prompt(team_selection_prompt)) .max_certs_behavior(MaxCertsBehavior::Prompt(cert_selection_prompt)) - .storage(Box::new(KeyringStorage::new( - "isideload-minimal".to_string(), - ))) .machine_name("isideload-minimal".to_string()) .build(); diff --git a/isideload/Cargo.toml b/isideload/Cargo.toml index b5aa2db..4f3c5ce 100644 --- a/isideload/Cargo.toml +++ b/isideload/Cargo.toml @@ -14,6 +14,7 @@ readme = "../README.md" default = ["install", "keyring-storage"] install = ["dep:idevice"] keyring-storage = ["keyring"] +fs-storage = [] # Unfortunately, dependencies are kinda a mess rn, since this requires a beta version of the srp crate. # Once that becomes stable, hopefuly duplicate dependencies should clean up.\ diff --git a/isideload/src/anisette/remote_v3/mod.rs b/isideload/src/anisette/remote_v3/mod.rs index 3665a81..e5ddebc 100644 --- a/isideload/src/anisette/remote_v3/mod.rs +++ b/isideload/src/anisette/remote_v3/mod.rs @@ -1,7 +1,5 @@ mod state; -use std::fs; -use std::path::PathBuf; use std::sync::Arc; use std::time::SystemTime; @@ -11,13 +9,14 @@ use reqwest::header::{CONTENT_TYPE, HeaderMap, HeaderValue}; use rootcause::prelude::*; use serde::Deserialize; use tokio_tungstenite::tungstenite::Message; -use tracing::{debug, info}; +use tracing::{debug, info, warn}; use crate::SideloadError; use crate::anisette::remote_v3::state::AnisetteState; use crate::anisette::{AnisetteClientInfo, AnisetteData, AnisetteProvider}; use crate::auth::grandslam::GrandSlam; use crate::util::plist::PlistDataExtract; +use crate::util::storage::{SideloadingStorage, new_storage}; use futures_util::{SinkExt, StreamExt}; pub const DEFAULT_ANISETTE_V3_URL: &str = "https://ani.stikstore.app"; @@ -25,7 +24,7 @@ pub const DEFAULT_ANISETTE_V3_URL: &str = "https://ani.stikstore.app"; pub struct RemoteV3AnisetteProvider { pub state: Option, url: String, - config_path: PathBuf, + storage: Box, serial_number: String, client_info: Option, client: reqwest::Client, @@ -36,14 +35,14 @@ impl RemoteV3AnisetteProvider { /// /// # Arguments /// - `url`: The URL of the remote anisette service - /// - `config_path`: The path to the config file + /// - `storage`: The storage backend for anisette data /// - `serial_number`: The serial number of the device /// - pub fn new(url: &str, config_path: PathBuf, serial_number: String) -> Self { + pub fn new(url: &str, storage: Box, serial_number: String) -> Self { Self { state: None, url: url.to_string(), - config_path, + storage, serial_number, client_info: None, client: reqwest::ClientBuilder::new() @@ -58,8 +57,8 @@ impl RemoteV3AnisetteProvider { self } - pub fn set_config_path(mut self, config_path: PathBuf) -> RemoteV3AnisetteProvider { - self.config_path = config_path; + pub fn set_storage(mut self, storage: Box) -> RemoteV3AnisetteProvider { + self.storage = storage; self } @@ -71,7 +70,11 @@ impl RemoteV3AnisetteProvider { impl Default for RemoteV3AnisetteProvider { fn default() -> Self { - Self::new(DEFAULT_ANISETTE_V3_URL, PathBuf::new(), "0".to_string()) + Self::new( + DEFAULT_ANISETTE_V3_URL, + Box::new(new_storage()), + "0".to_string(), + ) } } @@ -166,12 +169,15 @@ impl AnisetteProvider for RemoteV3AnisetteProvider { impl RemoteV3AnisetteProvider { async fn get_state(&mut self, gs: Arc) -> Result<&mut AnisetteState, Report> { - let state_path = self.config_path.join("state.plist"); - fs::create_dir_all(&self.config_path)?; if self.state.is_none() { - if let Ok(state) = plist::from_file(&state_path) { - info!("Loaded existing anisette state from {:?}", state_path); - self.state = Some(state); + if let Ok(Some(state)) = &self.storage.retrieve_data("anisette_state") { + if let Ok(state) = plist::from_bytes(state) { + info!("Loaded existing anisette state"); + self.state = Some(state); + } else { + warn!("Failed to parse existing anisette state, starting fresh"); + self.state = Some(AnisetteState::new()); + } } else { info!("No existing anisette state found"); self.state = Some(AnisetteState::new()); @@ -185,7 +191,11 @@ impl RemoteV3AnisetteProvider { .await .context("Failed to provision")?; } - plist::to_file_xml(&state_path, &state)?; + let buf = Vec::new(); + let mut writer = std::io::BufWriter::new(buf); + plist::to_writer_xml(&mut writer, &state).unwrap(); + self.storage + .store_data("anisette_state", &writer.into_inner()?)?; Ok(state) } diff --git a/isideload/src/util/fs_storage.rs b/isideload/src/util/fs_storage.rs new file mode 100644 index 0000000..4ccb959 --- /dev/null +++ b/isideload/src/util/fs_storage.rs @@ -0,0 +1,53 @@ +use std::path::{Path, PathBuf}; + +use rootcause::prelude::*; + +use crate::util::storage::SideloadingStorage; + +pub struct FsStorage { + path: PathBuf, +} + +impl FsStorage { + pub fn new(path: PathBuf) -> Self { + FsStorage { path } + } +} + +impl Default for FsStorage { + fn default() -> Self { + Self::new(PathBuf::from(".")) + } +} + +impl SideloadingStorage for FsStorage { + fn store_data(&self, key: &str, data: &[u8]) -> Result<(), Report> { + let path = self.path.join(key); + let parent = path.parent().unwrap_or(Path::new(".")); + std::fs::create_dir_all(parent).context("Failed to create storage directory")?; + std::fs::write(&path, data).context("Failed to write data to file")?; + + Ok(()) + } + + fn retrieve_data(&self, key: &str) -> Result>, Report> { + let path = self.path.join(key); + match std::fs::read(&path) { + Ok(data) => Ok(Some(data)), + Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None), + Err(e) => Err(report!(e).context("Failed to read data from file").into()), + } + } + + fn store(&self, key: &str, value: &str) -> Result<(), Report> { + self.store_data(key, value.as_bytes()) + } + + fn retrieve(&self, key: &str) -> Result, Report> { + match self.retrieve_data(key) { + Ok(Some(data)) => Ok(Some(String::from_utf8_lossy(&data).into_owned())), + Ok(None) => Ok(None), + Err(e) => Err(e), + } + } +} diff --git a/isideload/src/util/mod.rs b/isideload/src/util/mod.rs index 41d1f65..abc79df 100644 --- a/isideload/src/util/mod.rs +++ b/isideload/src/util/mod.rs @@ -1,4 +1,6 @@ pub mod device; +#[cfg(feature = "fs-storage")] +pub mod fs_storage; #[cfg(feature = "keyring-storage")] pub mod keyring_storage; pub mod plist; diff --git a/isideload/src/util/storage.rs b/isideload/src/util/storage.rs index 50ede4c..9e09a5a 100644 --- a/isideload/src/util/storage.rs +++ b/isideload/src/util/storage.rs @@ -27,11 +27,18 @@ pub trait SideloadingStorage: Send + Sync { pub fn new_storage() -> impl SideloadingStorage { #[cfg(feature = "keyring-storage")] { - crate::util::keyring_storage::KeyringStorage::default() + return crate::util::keyring_storage::KeyringStorage::default(); } - #[cfg(not(feature = "keyring-storage"))] + #[cfg(feature = "fs-storage")] { - InMemoryStorage::new() + return crate::util::fs_storage::FsStorage::default(); + } + #[cfg(not(any(feature = "keyring-storage", feature = "fs-storage")))] + { + tracing::warn!( + "Keyring storage not enabled, falling back to in-memory storage. This means that the anisette state and certificates will not be saved across runs. Enable the 'keyring-storage' or 'fs-storage' feature for persistance." + ); + return InMemoryStorage::new(); } }