use serde::Deserialize;
use std::collections::BTreeMap;
use std::path::{Component, Path};
pub const EXPECTED_MANIFEST_SCHEMA_VERSION: u32 = 1;
pub const STARLIGHT_MAP_SCHEMA: &str = "nsb-healpix-starlight-v1";
pub const STARLIGHT_MANIFEST_SCHEMA: &str = "nsb-starlight-runtime-manifest-v1";
pub const REQUIRED_RUNTIME_ASSETS: &[(&str, &str)] = &[
("airglow_palace_v1.dat", "nsb-airglow-palace-continuum-v1"),
("f107_store.json", "nsb-f107-store-v1"),
("moonlight_mie_nsb_v1.dat", "nsb-moonlight-mie-phase-v1"),
(
"moonlight_multiscatter_nsb_v1.dat",
"nsb-moonlight-multiscatter-v1",
),
("solar_spectrum.dat", "wavelength-nm_irradiance-w-m2-nm-v1"),
];
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
pub struct Manifest {
pub schema_version: u32,
pub assets: Vec<Asset>,
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
pub struct Asset {
pub path: String,
pub schema: String,
pub sha256: String,
pub source: String,
pub license: String,
pub generator: String,
pub generation_command: String,
pub validation_report: String,
pub calibration_status: String,
pub runtime_embedded: bool,
#[serde(default)]
pub header: BTreeMap<String, String>,
}
impl Asset {
pub fn is_valid_production_starlight_map(&self) -> bool {
((self.schema == STARLIGHT_MAP_SCHEMA && self.path.ends_with(".release.csv"))
|| (self.schema == "nsb-healpix-starlight-binary-v1"
&& self.path.ends_with(".release.bin")))
&& self.calibration_status.eq_ignore_ascii_case("production")
&& self.runtime_embedded
}
pub fn is_valid_production_starlight_manifest(&self) -> bool {
self.schema == STARLIGHT_MANIFEST_SCHEMA
&& self.calibration_status.eq_ignore_ascii_case("production")
&& self.runtime_embedded
&& self.path.ends_with(".manifest.toml")
}
pub fn is_starlight_release_map_claim(&self) -> bool {
(self.path.ends_with(".release.csv")
|| self.path.ends_with(".release.bin")
|| self.schema == STARLIGHT_MAP_SCHEMA
|| self.schema == "nsb-healpix-starlight-binary-v1")
&& (self.runtime_embedded || self.calibration_status.eq_ignore_ascii_case("production"))
}
pub fn is_starlight_release_manifest_claim(&self) -> bool {
(self.schema == STARLIGHT_MANIFEST_SCHEMA
|| (self.path.ends_with(".manifest.toml")
&& starlight_release_stem(&self.path).is_some()))
&& (self.runtime_embedded || self.calibration_status.eq_ignore_ascii_case("production"))
}
pub fn starlight_release_stem(&self) -> Option<&str> {
starlight_release_stem(&self.path)
}
}
pub fn starlight_release_stem(path: &str) -> Option<&str> {
path.strip_suffix(".release.csv")
.or_else(|| path.strip_suffix(".release.bin"))
.or_else(|| path.strip_suffix(".manifest.toml"))
}
pub fn is_safe_data_relative_path(path: &str) -> bool {
if path.is_empty() || path.contains('\0') {
return false;
}
if path.starts_with('/') || path.starts_with('\\') {
return false;
}
if path.as_bytes().get(1) == Some(&b':') {
return false;
}
let p = Path::new(path);
if p.is_absolute() {
return false;
}
let mut has_normal = false;
for component in p.components() {
match component {
Component::Normal(part) => {
if part.is_empty() {
return false;
}
has_normal = true;
}
Component::CurDir => {}
Component::ParentDir | Component::RootDir | Component::Prefix(_) => return false,
}
}
has_normal
}