1pub mod github;
19pub mod signature;
20pub mod uninstall;
21pub mod update;
22pub mod upgrade;
23
24use std::cmp::Ordering;
25
26pub fn target_triple() -> &'static str {
31 match std::env::consts::ARCH {
32 "aarch64" => "aarch64-unknown-linux-musl",
33 _ => "x86_64-unknown-linux-musl",
35 }
36}
37
38pub fn current_version() -> String {
40 format!("v{}", env!("CARGO_PKG_VERSION"))
41}
42
43pub fn normalize_tag(version: &str) -> String {
45 let v = version.trim();
46 if v.starts_with('v') {
47 v.to_string()
48 } else {
49 format!("v{v}")
50 }
51}
52
53pub fn parse_version(version: &str) -> Option<(u64, u64, u64)> {
56 let v = version.trim().trim_start_matches('v');
57 let core = v.split(['-', '+']).next().unwrap_or(v);
59 let mut parts = core.split('.');
60 let major = parts.next()?.parse().ok()?;
61 let minor = parts.next()?.parse().ok()?;
62 let patch = parts.next().unwrap_or("0").parse().ok()?;
63 if parts.next().is_some() {
64 return None;
65 }
66 Some((major, minor, patch))
67}
68
69pub fn compare_versions(a: &str, b: &str) -> Ordering {
72 match (parse_version(a), parse_version(b)) {
73 (Some(va), Some(vb)) => va.cmp(&vb),
74 _ => Ordering::Equal,
75 }
76}
77
78pub fn update_available(current: &str, latest: &str) -> bool {
80 compare_versions(current, latest) == Ordering::Less
81}
82
83pub fn asset_names_for_version(tag: &str, target: &str) -> (String, String) {
86 let tag = normalize_tag(tag);
87 let archive = format!("mnemo-{tag}-{target}.tar.gz");
88 let sha = format!("{archive}.sha256");
89 (archive, sha)
90}
91
92pub fn sha256_hex(data: &[u8]) -> String {
94 use sha2::{Digest, Sha256};
95 let mut hasher = Sha256::new();
96 hasher.update(data);
97 let digest = hasher.finalize();
98 digest.iter().map(|b| format!("{b:02x}")).collect()
99}
100
101pub fn parse_sha256_file(content: &str) -> Option<String> {
104 let token = content.split_whitespace().next()?;
105 let lower = token.to_lowercase();
106 if lower.len() == 64 && lower.chars().all(|c| c.is_ascii_hexdigit()) {
107 Some(lower)
108 } else {
109 None
110 }
111}
112
113pub fn verify_sha256(data: &[u8], expected_hex: &str) -> bool {
115 sha256_hex(data) == expected_hex.trim().to_lowercase()
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn comparaison_versions() {
124 assert_eq!(compare_versions("v0.4.0", "v0.5.0"), Ordering::Less);
125 assert_eq!(compare_versions("v0.5.0", "v0.4.0"), Ordering::Greater);
126 assert_eq!(compare_versions("v0.5.0", "v0.5.0"), Ordering::Equal);
127 assert_eq!(compare_versions("0.5", "v0.5.0"), Ordering::Equal);
129 assert_eq!(compare_versions("v0.5.1", "v0.5.0"), Ordering::Greater);
130 assert_eq!(compare_versions("v1.0.0", "v0.9.9"), Ordering::Greater);
131 }
132
133 #[test]
134 fn update_disponible() {
135 assert!(update_available("v0.4.0", "v0.5.0"));
136 assert!(!update_available("v0.5.0", "v0.5.0"));
137 assert!(!update_available("v0.5.0", "v0.4.0"));
138 }
139
140 #[test]
141 fn parse_version_robuste() {
142 assert_eq!(parse_version("v0.5.0"), Some((0, 5, 0)));
143 assert_eq!(parse_version("1.2.3"), Some((1, 2, 3)));
144 assert_eq!(parse_version("v0.5.0-rc1"), Some((0, 5, 0)));
145 assert_eq!(parse_version("0.5"), Some((0, 5, 0)));
146 assert_eq!(parse_version("abc"), None);
147 assert_eq!(parse_version("0.5.0.1"), None);
148 }
149
150 #[test]
151 fn cible_par_defaut() {
152 let t = target_triple();
154 assert!(t.contains("linux-musl"));
155 }
156
157 #[test]
158 fn noms_assets() {
159 let (archive, sha) = asset_names_for_version("v0.5.0", "x86_64-unknown-linux-musl");
160 assert_eq!(archive, "mnemo-v0.5.0-x86_64-unknown-linux-musl.tar.gz");
161 assert_eq!(sha, "mnemo-v0.5.0-x86_64-unknown-linux-musl.tar.gz.sha256");
162 let (archive2, _) = asset_names_for_version("0.5.0", "x86_64-unknown-linux-musl");
164 assert_eq!(archive2, "mnemo-v0.5.0-x86_64-unknown-linux-musl.tar.gz");
165 }
166
167 #[test]
168 fn sha256_ok_et_ko() {
169 let data = b"hello mnemo";
170 let hex = sha256_hex(data);
171 assert_eq!(hex.len(), 64);
172 assert!(verify_sha256(data, &hex));
173 assert!(verify_sha256(data, &hex.to_uppercase()));
174 assert!(!verify_sha256(data, &"0".repeat(64)));
175 assert!(!verify_sha256(b"autre contenu", &hex));
176 }
177
178 #[test]
179 fn parse_fichier_sha256() {
180 let hex = "a".repeat(64);
181 let line = format!("{hex} mnemo-v0.5.0-x86_64-unknown-linux-musl.tar.gz\n");
182 assert_eq!(parse_sha256_file(&line), Some(hex.clone()));
183 assert_eq!(parse_sha256_file(&hex), Some(hex.clone()));
185 assert_eq!(parse_sha256_file("pas un hash"), None);
187 assert_eq!(parse_sha256_file(""), None);
188 }
189}