Skip to main content

hdiff_update_core/
manifest.rs

1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4
5use crate::{Error, Result};
6
7pub const DEFAULT_ALGORITHM_NAME: &str = "hdiffpatch-v5-window-zstd";
8pub const MANIFEST_SIGNATURE_ALGORITHM: &str = "ed25519";
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
11#[serde(rename_all = "camelCase")]
12pub struct Artifact {
13    pub url: String,
14    pub sha256: String,
15    pub size: u64,
16    #[serde(default, skip_serializing_if = "Option::is_none")]
17    pub signature: Option<String>,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
21#[serde(rename_all = "camelCase")]
22pub struct DeltaArtifact {
23    #[serde(default, skip_serializing_if = "Option::is_none")]
24    pub from_version: Option<String>,
25    pub from_sha256: String,
26    pub url: String,
27    pub sha256: String,
28    pub size: u64,
29    #[serde(default, skip_serializing_if = "Option::is_none")]
30    pub target_sha256: Option<String>,
31    #[serde(default, skip_serializing_if = "Option::is_none")]
32    pub target_size: Option<u64>,
33    #[serde(default = "default_algorithm")]
34    pub algorithm: String,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
38#[serde(rename_all = "camelCase")]
39pub struct ManifestSignature {
40    #[serde(default = "default_signature_algorithm")]
41    pub algorithm: String,
42    #[serde(default, skip_serializing_if = "Option::is_none")]
43    pub key_id: Option<String>,
44    pub value: String,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
48#[serde(rename_all = "camelCase")]
49pub struct PlatformRelease {
50    pub full: Artifact,
51    #[serde(default)]
52    pub deltas: Vec<DeltaArtifact>,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
56#[serde(rename_all = "camelCase")]
57pub struct UpdateManifest {
58    pub version: String,
59    #[serde(default, skip_serializing_if = "Option::is_none")]
60    pub notes: Option<String>,
61    #[serde(default, skip_serializing_if = "Option::is_none")]
62    pub published_at: Option<String>,
63    #[serde(default, skip_serializing_if = "Option::is_none")]
64    pub signature: Option<ManifestSignature>,
65    pub platforms: BTreeMap<String, PlatformRelease>,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
69#[serde(tag = "kind", rename_all = "camelCase")]
70pub enum SelectedArtifact {
71    Delta { artifact: DeltaArtifact },
72    Full { artifact: Artifact },
73}
74
75impl UpdateManifest {
76    pub fn signing_payload(&self) -> Result<Vec<u8>> {
77        let mut unsigned = self.clone();
78        unsigned.signature = None;
79        Ok(serde_json::to_vec(&unsigned)?)
80    }
81}
82
83impl UpdateManifest {
84    pub fn platform(&self, platform: &str) -> Result<&PlatformRelease> {
85        self.platforms
86            .get(platform)
87            .ok_or_else(|| Error::MissingPlatform {
88                platform: platform.to_string(),
89            })
90    }
91
92    pub fn select_artifact(
93        &self,
94        platform: &str,
95        current_version: Option<&str>,
96        current_sha256: Option<&str>,
97    ) -> Result<SelectedArtifact> {
98        let platform_release = self.platform(platform)?;
99
100        if let Some(current_sha256) = current_sha256 {
101            if let Some(delta) = platform_release.deltas.iter().find(|delta| {
102                let hash_matches = delta.from_sha256.eq_ignore_ascii_case(current_sha256);
103                let version_matches = match (&delta.from_version, current_version) {
104                    (Some(from), Some(current)) => from == current,
105                    (Some(_), None) => true,
106                    (None, _) => true,
107                };
108                hash_matches && version_matches
109            }) {
110                return Ok(SelectedArtifact::Delta {
111                    artifact: delta.clone(),
112                });
113            }
114        }
115
116        Ok(SelectedArtifact::Full {
117            artifact: platform_release.full.clone(),
118        })
119    }
120}
121
122pub fn default_platform() -> String {
123    let os = if cfg!(target_os = "windows") {
124        "windows"
125    } else if cfg!(target_os = "macos") {
126        "darwin"
127    } else if cfg!(target_os = "linux") {
128        "linux"
129    } else {
130        std::env::consts::OS
131    };
132
133    let arch = if cfg!(target_arch = "x86_64") {
134        "x86_64"
135    } else if cfg!(target_arch = "aarch64") {
136        "aarch64"
137    } else {
138        std::env::consts::ARCH
139    };
140
141    format!("{os}-{arch}")
142}
143
144fn default_algorithm() -> String {
145    DEFAULT_ALGORITHM_NAME.to_string()
146}
147
148fn default_signature_algorithm() -> String {
149    MANIFEST_SIGNATURE_ALGORITHM.to_string()
150}