Skip to main content

restorekit/firmware/
mod.rs

1//! Firmware resolution, caching, and download.
2//!
3//! [`resolve`] looks up the correct IPSW for a model (ipsw.me, with Apple's mesu
4//! feed as a fallback); [`download`] fetches it resumably and verifies its
5//! checksum; and [`default_cache_dir`]/[`cached`] manage the on-disk cache with
6//! `.json` checksum sidecars. The shared [`Firmware`] type lives here.
7
8mod cache;
9mod download;
10mod resolve;
11
12use std::time::Duration;
13
14use serde::{Deserialize, Serialize};
15
16use crate::error::{Error, Result};
17
18pub use cache::{cache_path, cached, default_cache_dir};
19pub use download::download;
20pub use resolve::resolve;
21
22/// Resolved firmware metadata for a specific Mac model.
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct Firmware {
25    pub identifier: String,
26    pub version: String,
27    pub build: String,
28    pub url: String,
29    pub size: u64,
30    pub sha256: Option<String>,
31    pub sha1: Option<String>,
32    pub signed: bool,
33}
34
35impl Firmware {
36    /// Canonical cache filename, derived from the download URL's basename.
37    pub fn file_name(&self) -> String {
38        self.url
39            .rsplit('/')
40            .next()
41            .filter(|s| s.ends_with(".ipsw"))
42            .map(str::to_string)
43            .unwrap_or_else(|| format!("UniversalMac_{}_{}_Restore.ipsw", self.version, self.build))
44    }
45}
46
47/// Shared blocking HTTP client used by resolution and download.
48fn http_client() -> Result<reqwest::blocking::Client> {
49    reqwest::blocking::Client::builder()
50        .timeout(Duration::from_secs(60))
51        .user_agent(concat!("restorekit/", env!("CARGO_PKG_VERSION")))
52        .build()
53        .map_err(Error::Http)
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    fn fw() -> Firmware {
61        Firmware {
62            identifier: "MacBookAir10,1".into(),
63            version: "26.5.2".into(),
64            build: "25F84".into(),
65            url: "https://updates.cdn-apple.com/x/UniversalMac_26.5.2_25F84_Restore.ipsw".into(),
66            size: 100,
67            sha256: None,
68            sha1: None,
69            signed: true,
70        }
71    }
72
73    #[test]
74    fn file_name_from_url() {
75        assert_eq!(fw().file_name(), "UniversalMac_26.5.2_25F84_Restore.ipsw");
76    }
77
78    #[test]
79    fn file_name_fallback_when_url_has_no_ipsw() {
80        let mut f = fw();
81        f.url = "https://example.com/redirect".into();
82        assert_eq!(f.file_name(), "UniversalMac_26.5.2_25F84_Restore.ipsw");
83    }
84}