lean_ctx/core/
integrity.rs1use std::collections::hash_map::DefaultHasher;
9use std::hash::{Hash, Hasher};
10
11const INTEGRITY_SEED: &str = "lean-ctx";
12const ORIGIN_REPO: &str = env!("CARGO_PKG_REPOSITORY");
13const ORIGIN_NAME: &str = env!("CARGO_PKG_NAME");
14const PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
15
16fn compute_seed_hash(seed: &str) -> u64 {
17 let mut hasher = DefaultHasher::new();
18 seed.hash(&mut hasher);
19 hasher.finish()
20}
21
22fn expected_hash() -> u64 {
23 compute_seed_hash("lean-ctx")
24}
25
26pub(crate) fn verify_integrity() -> bool {
27 compute_seed_hash(INTEGRITY_SEED) == expected_hash()
28}
29
30pub(crate) fn is_official_origin() -> bool {
31 ORIGIN_REPO.contains("yvgude/lean-ctx") && ORIGIN_NAME == "lean-ctx-engine"
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub struct BuildInfo {
40 pub kind: &'static str,
41 pub commit: &'static str,
43 pub dirty: bool,
45}
46
47const SHORT_COMMIT: usize = 10;
49
50impl BuildInfo {
51 pub fn short_commit(&self) -> &'static str {
52 self.commit.get(..SHORT_COMMIT).unwrap_or(self.commit)
53 }
54
55 pub fn is_official(&self) -> bool {
56 self.kind == "official"
57 }
58
59 pub fn label(&self) -> String {
61 if self.is_official() {
62 return "official".to_owned();
63 }
64 let mut label = self.kind.to_owned();
65 if !self.commit.is_empty() {
66 label.push(' ');
67 label.push_str(self.short_commit());
68 }
69 if self.dirty {
70 label.push_str(", dirty");
71 }
72 label
73 }
74
75 pub fn to_json(&self) -> serde_json::Value {
78 serde_json::json!({
79 "kind": self.kind,
80 "commit": (!self.commit.is_empty()).then_some(self.commit),
81 "dirty": self.dirty,
82 "official": self.is_official(),
83 })
84 }
85}
86
87const ENGINE_BUILD: BuildInfo = BuildInfo {
90 kind: env!("LEANCTX_BUILD_KIND"),
91 commit: env!("LEANCTX_BUILD_COMMIT"),
92 dirty: matches!(env!("LEANCTX_BUILD_DIRTY").as_bytes(), b"1"),
93};
94
95static BINARY_STAMP: std::sync::OnceLock<(&'static str, bool)> = std::sync::OnceLock::new();
97
98pub fn set_binary_stamp(commit: &'static str, dirty: bool) {
104 if !commit.is_empty() {
105 let _ = BINARY_STAMP.set((commit, dirty));
106 }
107}
108
109pub fn build() -> BuildInfo {
111 match BINARY_STAMP.get() {
112 Some(&(commit, dirty)) if !ENGINE_BUILD.is_official() => BuildInfo {
113 commit,
114 dirty,
115 ..ENGINE_BUILD
116 },
117 _ => ENGINE_BUILD,
118 }
119}
120
121pub struct IntegrityReport {
122 pub seed_ok: bool,
123 pub origin_ok: bool,
124 pub repo: &'static str,
125 pub pkg_name: &'static str,
126 pub version: &'static str,
127 pub build: BuildInfo,
128}
129
130pub fn check() -> IntegrityReport {
131 IntegrityReport {
132 seed_ok: verify_integrity(),
133 origin_ok: is_official_origin(),
134 repo: ORIGIN_REPO,
135 pkg_name: ORIGIN_NAME,
136 version: PKG_VERSION,
137 build: build(),
138 }
139}
140
141pub fn version_json() -> serde_json::Value {
143 let report = check();
144 serde_json::json!({
145 "name": "lean-ctx",
146 "version": report.version,
147 "repository": report.repo,
148 "unmodified": report.seed_ok && report.origin_ok,
149 "build": report.build.to_json(),
150 })
151}
152
153pub fn origin_line() -> String {
154 let report = check();
155 if report.seed_ok && report.origin_ok && report.build.is_official() {
156 format!("lean-ctx {} (official, {})", report.version, report.repo)
157 } else if report.seed_ok && report.origin_ok {
158 format!("lean-ctx {} ({})", report.version, report.build.label())
159 } else {
160 format!(
161 "WARNING: Modified redistribution detected. \
162 Official builds: https://github.com/yvgude/lean-ctx \
163 (pkg={}, repo={})",
164 report.pkg_name, report.repo
165 )
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[test]
174 fn integrity_passes_in_official_build() {
175 assert!(verify_integrity());
176 }
177
178 #[test]
179 fn seed_hash_is_deterministic() {
180 let h1 = compute_seed_hash("lean-ctx");
181 let h2 = compute_seed_hash("lean-ctx");
182 assert_eq!(h1, h2);
183 }
184
185 #[test]
186 fn tampered_seed_fails() {
187 let tampered = compute_seed_hash("better-ctx");
188 assert_ne!(tampered, expected_hash());
189 }
190
191 #[test]
192 fn origin_is_official() {
193 assert!(is_official_origin());
194 }
195
196 #[test]
197 fn origin_line_contains_version() {
198 let line = origin_line();
199 assert!(line.contains(env!("CARGO_PKG_VERSION")));
200 }
201
202 #[test]
203 fn build_kind_labels_name_only_release_builds_official() {
204 let sha = "f78aff8dc5a1b2c3d4e5f60718293a4b5c6d7e8f";
205 let b = |kind, commit, dirty| BuildInfo {
206 kind,
207 commit,
208 dirty,
209 };
210 assert_eq!(b("official", sha, false).label(), "official");
211 assert_eq!(b("dogfood", sha, false).label(), "dogfood f78aff8dc5");
212 assert_eq!(b("dev", sha, true).label(), "dev f78aff8dc5, dirty");
213 assert_eq!(b("source", "", false).label(), "source");
214 assert_eq!(b("dev", sha, false).to_json()["official"], false);
215 assert_eq!(
216 b("source", "", false).to_json()["commit"],
217 serde_json::Value::Null
218 );
219
220 let this = build();
223 assert!(
224 matches!(this.kind, "official" | "dev" | "dogfood" | "source"),
225 "{this:?}"
226 );
227 assert!(
228 this.commit.is_empty() || this.commit.len() == 40,
229 "{this:?}"
230 );
231 assert_eq!(
232 origin_line().contains("(official,"),
233 this.is_official(),
234 "{}",
235 origin_line()
236 );
237 assert_eq!(version_json()["build"]["kind"], this.kind);
238 }
239}