use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use crate::{Error, Result};
pub const DEFAULT_ALGORITHM_NAME: &str = "hdiffpatch-v5-window-zstd";
pub const MANIFEST_SIGNATURE_ALGORITHM: &str = "ed25519";
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Artifact {
pub url: String,
pub sha256: String,
pub size: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub signature: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DeltaArtifact {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub from_version: Option<String>,
pub from_sha256: String,
pub url: String,
pub sha256: String,
pub size: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub target_sha256: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub target_size: Option<u64>,
#[serde(default = "default_algorithm")]
pub algorithm: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ManifestSignature {
#[serde(default = "default_signature_algorithm")]
pub algorithm: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub key_id: Option<String>,
pub value: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PlatformRelease {
pub full: Artifact,
#[serde(default)]
pub deltas: Vec<DeltaArtifact>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UpdateManifest {
pub version: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub notes: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub published_at: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub signature: Option<ManifestSignature>,
pub platforms: BTreeMap<String, PlatformRelease>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "camelCase")]
pub enum SelectedArtifact {
Delta { artifact: DeltaArtifact },
Full { artifact: Artifact },
}
impl UpdateManifest {
pub fn signing_payload(&self) -> Result<Vec<u8>> {
let mut unsigned = self.clone();
unsigned.signature = None;
Ok(serde_json::to_vec(&unsigned)?)
}
}
impl UpdateManifest {
pub fn platform(&self, platform: &str) -> Result<&PlatformRelease> {
self.platforms
.get(platform)
.ok_or_else(|| Error::MissingPlatform {
platform: platform.to_string(),
})
}
pub fn select_artifact(
&self,
platform: &str,
current_version: Option<&str>,
current_sha256: Option<&str>,
) -> Result<SelectedArtifact> {
let platform_release = self.platform(platform)?;
if let Some(current_sha256) = current_sha256 {
if let Some(delta) = platform_release.deltas.iter().find(|delta| {
let hash_matches = delta.from_sha256.eq_ignore_ascii_case(current_sha256);
let version_matches = match (&delta.from_version, current_version) {
(Some(from), Some(current)) => from == current,
(Some(_), None) => true,
(None, _) => true,
};
hash_matches && version_matches
}) {
return Ok(SelectedArtifact::Delta {
artifact: delta.clone(),
});
}
}
Ok(SelectedArtifact::Full {
artifact: platform_release.full.clone(),
})
}
}
pub fn default_platform() -> String {
let os = if cfg!(target_os = "windows") {
"windows"
} else if cfg!(target_os = "macos") {
"darwin"
} else if cfg!(target_os = "linux") {
"linux"
} else {
std::env::consts::OS
};
let arch = if cfg!(target_arch = "x86_64") {
"x86_64"
} else if cfg!(target_arch = "aarch64") {
"aarch64"
} else {
std::env::consts::ARCH
};
format!("{os}-{arch}")
}
fn default_algorithm() -> String {
DEFAULT_ALGORITHM_NAME.to_string()
}
fn default_signature_algorithm() -> String {
MANIFEST_SIGNATURE_ALGORITHM.to_string()
}