metrale_bench/
serve_identity.rs1use std::path::Path;
29
30use anyhow::{Context, Result};
31use sha2::{Digest, Sha256};
32
33#[must_use]
36pub fn argv_fingerprint(args_after_program: &[String]) -> String {
37 let mut h = Sha256::new();
38 for a in args_after_program {
39 h.update(a.as_bytes());
40 h.update([0u8]);
41 }
42 metrale_closure::hex_lower(&h.finalize())
43}
44
45pub fn file_sha256(path: &Path) -> Result<String> {
47 let mut f = std::fs::File::open(path).with_context(|| format!("opening {}", path.display()))?;
48 let mut h = Sha256::new();
49 let mut buf = vec![0u8; 1 << 16];
52 loop {
53 match std::io::Read::read(&mut f, &mut buf) {
54 Ok(0) => break,
55 Ok(n) => h.update(&buf[..n]),
56 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => {}
57 Err(e) => return Err(e).with_context(|| format!("reading {}", path.display())),
58 }
59 }
60 Ok(metrale_closure::hex_lower(&h.finalize()))
61}
62
63pub fn this_process() -> &'static ServeIdentity {
67 static ID: std::sync::OnceLock<ServeIdentity> = std::sync::OnceLock::new();
68 ID.get_or_init(|| {
69 let args: Vec<String> = std::env::args().skip(1).collect();
70 ServeIdentity {
71 argv_sha256: argv_fingerprint(&args),
72 binary_sha256: std::env::current_exe()
73 .context("current_exe")
74 .and_then(|p| file_sha256(&p))
75 .unwrap_or_else(|e| format!("unavailable: {e:#}")),
76 env_sha256: crate::serve_env::fingerprint(&crate::serve_env::process_levers()),
77 pid: std::process::id(),
78 }
79 })
80}
81
82#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
84pub struct ServeIdentity {
85 pub argv_sha256: String,
86 pub binary_sha256: String,
87 #[serde(default)]
96 pub env_sha256: String,
97 pub pid: u32,
98}
99
100#[must_use]
104pub fn env_is_unknown(reported: &str) -> bool {
105 reported.is_empty()
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn the_fingerprint_separates_arguments_and_orders_them() {
114 let a = argv_fingerprint(&["serve".into(), "m".into(), "--port".into(), "1".into()]);
115 let b = argv_fingerprint(&["serve".into(), "m".into(), "--port".into(), "2".into()]);
116 let c = argv_fingerprint(&["serve m".into(), "--port".into(), "1".into()]);
117 assert_ne!(a, b, "a different port is a different server");
118 assert_ne!(a, c, "joined arguments are not the same argv");
119 assert_eq!(
120 a,
121 argv_fingerprint(&["serve".into(), "m".into(), "--port".into(), "1".into()])
122 );
123 assert_eq!(a.len(), 64);
124 }
125
126 #[test]
130 fn the_identity_carries_the_lever_digest_and_an_older_server_reports_none() {
131 let id = this_process();
132 assert_eq!(
133 id.env_sha256,
134 crate::serve_env::fingerprint(&crate::serve_env::process_levers())
135 );
136 assert_eq!(id.env_sha256.len(), 64);
137 let old: ServeIdentity =
138 serde_json::from_str(r#"{"argv_sha256":"a","binary_sha256":"b","pid":1}"#).unwrap();
139 assert_eq!(old.env_sha256, "");
140 assert_ne!(
141 old.env_sha256,
142 crate::serve_env::fingerprint(&Default::default()),
143 "even an empty lever set has a digest an old server cannot claim"
144 );
145 }
146
147 #[test]
148 fn a_file_digest_is_its_bytes() {
149 let dir = std::env::temp_dir().join(format!("serve-identity-{}", std::process::id()));
150 std::fs::create_dir_all(&dir).unwrap();
151 let p = dir.join("bin");
152 std::fs::write(&p, b"hello").unwrap();
153 assert_eq!(
154 file_sha256(&p).unwrap(),
155 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
156 );
157 std::fs::write(&p, b"hellO").unwrap();
158 assert_ne!(
159 file_sha256(&p).unwrap(),
160 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
161 );
162 let big: Vec<u8> = (0..200_000u32).map(|i| (i % 251) as u8).collect();
164 std::fs::write(&p, &big).unwrap();
165 assert_eq!(
166 file_sha256(&p).unwrap(),
167 metrale_closure::hex_lower(&Sha256::digest(&big))
168 );
169 let _ = std::fs::remove_dir_all(&dir);
170 }
171}