receipt_bench/
fingerprint.rs1use sha2::{Digest, Sha256};
4use std::env;
5use std::fs;
6use std::process::Command;
7
8#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
13pub struct MachineFingerprint(String);
14
15impl MachineFingerprint {
16 pub fn generate() -> Self {
18 let mut hasher = Sha256::new();
19
20 let hostname = hostname().unwrap_or_else(|| "unknown".to_string());
22 hasher.update(hostname.as_bytes());
23
24 let username = username().unwrap_or_else(|| "unknown".to_string());
26 hasher.update(username.as_bytes());
27
28 let arch = env::consts::ARCH;
30 hasher.update(arch.as_bytes());
31
32 let os = env::consts::OS;
34 hasher.update(os.as_bytes());
35
36 let cpus = num_cpus().min(256);
38 hasher.update([cpus as u8]);
39
40 let page_size = page_size_log();
42 hasher.update([page_size as u8]);
43
44 if let Some(machine_id) = machine_id() {
46 hasher.update(machine_id.as_bytes());
47 }
48
49 let result = hasher.finalize();
50 let hex = encode_hex(&result);
51
52 MachineFingerprint(hex[..64].to_string())
54 }
55
56 #[cfg(test)]
58 pub fn from_hex(hex: &str) -> Self {
59 MachineFingerprint(hex[..64].to_string())
60 }
61
62 #[inline]
64 pub fn as_str(&self) -> &str {
65 &self.0
66 }
67}
68
69impl Default for MachineFingerprint {
70 fn default() -> Self {
71 Self::generate()
72 }
73}
74
75impl std::fmt::Display for MachineFingerprint {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 write!(f, "{}", self.0)
78 }
79}
80
81fn hostname() -> Option<String> {
84 #[cfg(unix)]
85 {
86 Command::new("hostname")
87 .output()
88 .ok()
89 .filter(|o| o.status.success())
90 .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
91 }
92 #[cfg(not(unix))]
93 {
94 None
95 }
96}
97
98fn username() -> Option<String> {
99 env::var("USER").or_else(|_| env::var("USERNAME")).ok()
100}
101
102fn num_cpus() -> usize {
103 std::thread::available_parallelism()
104 .map(|p| p.get())
105 .unwrap_or(1)
106}
107
108fn page_size_log() -> usize {
109 #[cfg(unix)]
112 {
113 use std::fs;
114 if let Ok(content) = fs::read_to_string("/proc/sys/kernel/osureset_page_size") {
115 if let Ok(size) = content.trim().parse::<usize>() {
116 let mut log = 0usize;
117 let mut s = size;
118 while s > 1 {
119 s /= 2;
120 log += 1;
121 }
122 return log.min(16);
123 }
124 }
125 12
126 }
127 #[cfg(not(unix))]
128 {
129 12
130 }
131}
132
133fn machine_id() -> Option<String> {
134 #[cfg(unix)]
135 {
136 let paths = ["/etc/machine-id", "/var/lib/dbus/machine-id"];
137 for path in &paths {
138 if let Ok(content) = fs::read_to_string(path) {
139 let trimmed = content.trim();
140 if !trimmed.is_empty() {
141 return Some(trimmed.to_string());
142 }
143 }
144 }
145 None
146 }
147 #[cfg(not(unix))]
148 {
149 None
150 }
151}
152
153fn encode_hex(data: &[u8]) -> String {
155 const HEX_CHARS: &[u8; 16] = b"0123456789abcdef";
156 let mut s = String::with_capacity(data.len() * 2);
157 for &b in data {
158 s.push(HEX_CHARS[(b >> 4) as usize] as char);
159 s.push(HEX_CHARS[(b & 0xf) as usize] as char);
160 }
161 s
162}