Skip to main content

libdd_common/machine_id/
mod.rs

1// Copyright 2021-Present Datadog, Inc. https://www.datadoghq.com/
2// SPDX-License-Identifier: Apache-2.0
3
4//! Host machine identifier, mirroring `pkg/util/uuid.GetUUID()` in the Go agent.
5//!
6//! | Platform | Source |
7//! |----------|--------|
8//! | Linux    | `/sys/class/dmi/id/product_uuid` then `/etc/machine-id` → `/proc/sys/kernel/random/boot_id` |
9//! | macOS    | `gethostuuid(3)` |
10//! | Windows  | `HKLM\SOFTWARE\Microsoft\Cryptography\MachineGuid` |
11//! | Other    | `""` |
12//!
13//! All values are normalised to lowercase `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`.
14//! Returns `""` on failure rather than a random UUID — the backend can detect
15//! a missing value but not a wrong one.
16
17use std::sync::LazyLock;
18
19#[cfg(target_os = "linux")]
20mod linux;
21
22#[cfg(target_os = "macos")]
23mod macos;
24
25#[cfg(windows)]
26mod windows;
27
28/// Normalise a raw OS machine-id to a lowercase hyphenated UUID string.
29/// Strips hyphens, filters to hex digits, lowercases, then re-inserts hyphens.
30/// Returns `""` if the result is not exactly 32 hex digits.
31pub(crate) fn normalize_uuid(raw: &str) -> String {
32    let hex: String = raw
33        .chars()
34        .filter(|c| c.is_ascii_hexdigit())
35        .flat_map(char::to_lowercase)
36        .collect();
37
38    if hex.len() != 32 {
39        return String::new();
40    }
41
42    format!(
43        "{}-{}-{}-{}-{}",
44        &hex[0..8],
45        &hex[8..12],
46        &hex[12..16],
47        &hex[16..20],
48        &hex[20..32],
49    )
50}
51
52static MACHINE_ID: LazyLock<String> = LazyLock::new(|| {
53    let raw = {
54        #[cfg(target_os = "linux")]
55        {
56            linux::get_machine_id_impl()
57        }
58        #[cfg(target_os = "macos")]
59        {
60            macos::get_machine_id_impl()
61        }
62        #[cfg(windows)]
63        {
64            windows::get_machine_id_impl()
65        }
66        #[cfg(not(any(target_os = "linux", target_os = "macos", windows)))]
67        {
68            String::new()
69        }
70    };
71    normalize_uuid(&raw)
72});
73
74/// Returns the host machine ID as a lowercase hyphenated UUID, cached for the process lifetime.
75/// Returns `""` on failure or unsupported platforms.
76pub fn get_machine_id() -> &'static str {
77    MACHINE_ID.as_str()
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn cached_value_is_stable() {
86        assert_eq!(get_machine_id(), get_machine_id());
87    }
88
89    #[test]
90    fn value_has_uuid_shape_if_nonempty() {
91        let id = get_machine_id();
92        if id.is_empty() {
93            return;
94        }
95        assert_eq!(id.len(), 36);
96        for (i, c) in id.chars().enumerate() {
97            if [8, 13, 18, 23].contains(&i) {
98                assert_eq!(c, '-');
99            } else {
100                assert!(c.is_ascii_hexdigit() && !c.is_ascii_uppercase());
101            }
102        }
103    }
104
105    #[test]
106    fn normalize_bare_hex_inserts_hyphens() {
107        assert_eq!(
108            normalize_uuid("b08fa8a2b01a4d2bbd95fec7e30c5aec"),
109            "b08fa8a2-b01a-4d2b-bd95-fec7e30c5aec"
110        );
111    }
112
113    #[test]
114    fn normalize_uppercase_uuid_lowercased() {
115        assert_eq!(
116            normalize_uuid("B08FA8A2-B01A-4D2B-BD95-FEC7E30C5AEC"),
117            "b08fa8a2-b01a-4d2b-bd95-fec7e30c5aec"
118        );
119    }
120
121    #[test]
122    fn normalize_lowercase_uuid_unchanged() {
123        assert_eq!(
124            normalize_uuid("b08fa8a2-b01a-4d2b-bd95-fec7e30c5aec"),
125            "b08fa8a2-b01a-4d2b-bd95-fec7e30c5aec"
126        );
127    }
128
129    #[test]
130    fn normalize_invalid_returns_empty() {
131        assert_eq!(normalize_uuid(""), "");
132        assert_eq!(normalize_uuid("b08fa8a2"), "");
133        assert_eq!(normalize_uuid("zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"), "");
134    }
135}