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,
},
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();

View File

@@ -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.\

View File

@@ -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<AnisetteState>,
url: String,
config_path: PathBuf,
storage: Box<dyn SideloadingStorage>,
serial_number: String,
client_info: Option<AnisetteClientInfo>,
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<dyn SideloadingStorage>, 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<dyn SideloadingStorage>) -> 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<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 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)
}

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;
#[cfg(feature = "fs-storage")]
pub mod fs_storage;
#[cfg(feature = "keyring-storage")]
pub mod keyring_storage;
pub mod plist;

View File

@@ -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();
}
}