Use storage provider for anisette and provide fs-storage implementation

This commit is contained in:
nab138
2026-02-14 17:23:52 -05:00
parent 1958c10b2d
commit 32cc042c07
6 changed files with 92 additions and 23 deletions

View File

@@ -9,7 +9,6 @@ use isideload::{
teams::DeveloperTeam, teams::DeveloperTeam,
}, },
sideload::{SideloaderBuilder, TeamSelection, builder::MaxCertsBehavior}, sideload::{SideloaderBuilder, TeamSelection, builder::MaxCertsBehavior},
util::keyring_storage::KeyringStorage,
}; };
use tracing::Level; use tracing::Level;
@@ -120,9 +119,6 @@ async fn main() {
let mut sideloader = SideloaderBuilder::new(dev_session, apple_id.to_string()) let mut sideloader = SideloaderBuilder::new(dev_session, apple_id.to_string())
.team_selection(TeamSelection::Prompt(team_selection_prompt)) .team_selection(TeamSelection::Prompt(team_selection_prompt))
.max_certs_behavior(MaxCertsBehavior::Prompt(cert_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()) .machine_name("isideload-minimal".to_string())
.build(); .build();

View File

@@ -14,6 +14,7 @@ readme = "../README.md"
default = ["install", "keyring-storage"] default = ["install", "keyring-storage"]
install = ["dep:idevice"] install = ["dep:idevice"]
keyring-storage = ["keyring"] keyring-storage = ["keyring"]
fs-storage = []
# Unfortunately, dependencies are kinda a mess rn, since this requires a beta version of the srp crate. # 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.\ # Once that becomes stable, hopefuly duplicate dependencies should clean up.\

View File

@@ -1,7 +1,5 @@
mod state; mod state;
use std::fs;
use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use std::time::SystemTime; use std::time::SystemTime;
@@ -11,13 +9,14 @@ use reqwest::header::{CONTENT_TYPE, HeaderMap, HeaderValue};
use rootcause::prelude::*; use rootcause::prelude::*;
use serde::Deserialize; use serde::Deserialize;
use tokio_tungstenite::tungstenite::Message; use tokio_tungstenite::tungstenite::Message;
use tracing::{debug, info}; use tracing::{debug, info, warn};
use crate::SideloadError; use crate::SideloadError;
use crate::anisette::remote_v3::state::AnisetteState; use crate::anisette::remote_v3::state::AnisetteState;
use crate::anisette::{AnisetteClientInfo, AnisetteData, AnisetteProvider}; use crate::anisette::{AnisetteClientInfo, AnisetteData, AnisetteProvider};
use crate::auth::grandslam::GrandSlam; use crate::auth::grandslam::GrandSlam;
use crate::util::plist::PlistDataExtract; use crate::util::plist::PlistDataExtract;
use crate::util::storage::{SideloadingStorage, new_storage};
use futures_util::{SinkExt, StreamExt}; use futures_util::{SinkExt, StreamExt};
pub const DEFAULT_ANISETTE_V3_URL: &str = "https://ani.stikstore.app"; 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 struct RemoteV3AnisetteProvider {
pub state: Option<AnisetteState>, pub state: Option<AnisetteState>,
url: String, url: String,
config_path: PathBuf, storage: Box<dyn SideloadingStorage>,
serial_number: String, serial_number: String,
client_info: Option<AnisetteClientInfo>, client_info: Option<AnisetteClientInfo>,
client: reqwest::Client, client: reqwest::Client,
@@ -36,14 +35,14 @@ impl RemoteV3AnisetteProvider {
/// ///
/// # Arguments /// # Arguments
/// - `url`: The URL of the remote anisette service /// - `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 /// - `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<dyn SideloadingStorage>, serial_number: String) -> Self {
Self { Self {
state: None, state: None,
url: url.to_string(), url: url.to_string(),
config_path, storage,
serial_number, serial_number,
client_info: None, client_info: None,
client: reqwest::ClientBuilder::new() client: reqwest::ClientBuilder::new()
@@ -58,8 +57,8 @@ impl RemoteV3AnisetteProvider {
self self
} }
pub fn set_config_path(mut self, config_path: PathBuf) -> RemoteV3AnisetteProvider { pub fn set_storage(mut self, storage: Box<dyn SideloadingStorage>) -> RemoteV3AnisetteProvider {
self.config_path = config_path; self.storage = storage;
self self
} }
@@ -71,7 +70,11 @@ impl RemoteV3AnisetteProvider {
impl Default for RemoteV3AnisetteProvider { impl Default for RemoteV3AnisetteProvider {
fn default() -> Self { 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 { impl RemoteV3AnisetteProvider {
async fn get_state(&mut self, gs: Arc<GrandSlam>) -> Result<&mut AnisetteState, Report> { async fn get_state(&mut self, gs: Arc<GrandSlam>) -> 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 self.state.is_none() {
if let Ok(state) = plist::from_file(&state_path) { if let Ok(Some(state)) = &self.storage.retrieve_data("anisette_state") {
info!("Loaded existing anisette state from {:?}", state_path); if let Ok(state) = plist::from_bytes(state) {
self.state = Some(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 { } else {
info!("No existing anisette state found"); info!("No existing anisette state found");
self.state = Some(AnisetteState::new()); self.state = Some(AnisetteState::new());
@@ -185,7 +191,11 @@ impl RemoteV3AnisetteProvider {
.await .await
.context("Failed to provision")?; .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) Ok(state)
} }

View File

@@ -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<Option<Vec<u8>>, 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<Option<String>, 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),
}
}
}

View File

@@ -1,4 +1,6 @@
pub mod device; pub mod device;
#[cfg(feature = "fs-storage")]
pub mod fs_storage;
#[cfg(feature = "keyring-storage")] #[cfg(feature = "keyring-storage")]
pub mod keyring_storage; pub mod keyring_storage;
pub mod plist; pub mod plist;

View File

@@ -27,11 +27,18 @@ pub trait SideloadingStorage: Send + Sync {
pub fn new_storage() -> impl SideloadingStorage { pub fn new_storage() -> impl SideloadingStorage {
#[cfg(feature = "keyring-storage")] #[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();
} }
} }