1use super::{Client, Error, Node};
2use crate::client::{DEFAULT_TIMEOUT_SECS, Format};
3use std::{sync::Arc, time::Duration};
4use uuid::Uuid;
5
6#[derive(Clone)]
7pub struct System {
8 client: Client,
9 pub id: Uuid,
10}
11
12impl System {
13 pub(crate) fn new(client: Client, id: Uuid) -> Self {
14 Self { client, id }
15 }
16
17 pub fn node(&self, id: &str, name: &str, upstream: bool, token: Option<String>) -> Result<Arc<Node>, Error> {
18 let upstream_arg = if upstream { "yes".to_string() } else { "no".to_string() };
19 let token_arg = token.unwrap_or("$uuid".to_string());
20 let args = vec![
21 self.id.to_string(),
22 id.to_string(),
23 name.to_string(),
24 upstream_arg,
25 token_arg,
26 ];
27 let mut client = self.client.clone();
28 let transaction = client.send(Format::Json, "nodes", &args)?;
29 let _res = client.wait_for_response(transaction, Duration::from_secs(DEFAULT_TIMEOUT_SECS))?;
30 Ok(Arc::new(Node::new(client, self.clone(), id.to_string())))
31 }
32}
33
34#[cfg(target_os = "macos")]
35#[allow(unused)]
36pub fn get_system_id() -> Result<Uuid, Error> {
37 use serde::Deserialize;
38 use std::process::Command;
39 use std::str::FromStr;
40
41 #[derive(Deserialize)]
42 struct HardwareOverview {
43 #[serde(rename = "platform_UUID")]
44 platform_uuid: String,
45 }
46
47 #[derive(Deserialize)]
48 struct Wrapper {
49 #[serde(rename = "SPHardwareDataType")]
50 sp_hardware_data_type: Vec<HardwareOverview>,
51 }
52
53 let output = Command::new("system_profiler")
54 .arg("SPHardwareDataType")
55 .arg("-json")
56 .output()
57 .map_err(|e| Error::Default("Failed invoking system_profiler to lookup SPHardwareDataType".to_string()))?;
58 let wrapper = serde_json::from_slice::<Wrapper>(&output.stdout).map_err(|e| {
59 Error::Serialization("Failed parsing system_profiler SPHardwareDataType lookup response".to_string())
60 })?;
61 match wrapper.sp_hardware_data_type.first() {
62 None => Err(Error::Serialization(
63 "Failed finding a hardware datatype in system_profiler SPHardwareDataType lookup".to_string(),
64 )),
65 Some(hardware_overview) => {
66 let id = Uuid::from_str(&hardware_overview.platform_uuid)
67 .map_err(|e| Error::Serialization("Failed parsing uuid".to_string()))?;
68 Ok(id)
69 }
70 }
71}
72
73#[cfg(target_os = "linux")]
74#[allow(unused)]
75pub fn get_system_id() -> Result<Uuid, Error> {
76 let model = get_model()?;
77 let serial_number = get_serial_number()?;
78 let model_plus_serial_number = format!("{model}:{serial_number}");
79 let id = Uuid::new_v5(&Uuid::NAMESPACE_OID, model_plus_serial_number.as_bytes());
80 Ok(id)
81}
82
83#[cfg(target_os = "linux")]
84fn get_model() -> Result<String, Error> {
85 const MODEL_FILENAME: &str = "/sys/firmware/devicetree/base/model";
86 std::fs::read_to_string(MODEL_FILENAME)
87 .map_err(|e| Error::Io(format!("Failed reading {MODEL_FILENAME} to obtain model ({e:?})")))
88}
89
90#[cfg(target_os = "linux")]
91fn get_serial_number() -> Result<String, Error> {
92 const SN_FILENAME: &str = "/sys/firmware/devicetree/base/serial-number";
93 std::fs::read_to_string(SN_FILENAME)
94 .map_err(|e| Error::Io(format!("Failed reading {SN_FILENAME} to obtain serial number ({e:?})")))
95}
96
97#[cfg(test)]
98mod tests {
99 use crate::system::get_system_id;
100
101 #[test]
102 #[cfg(target_os = "macos")]
103 fn can_get_system_id_on_macos() {
104 let _system_id = get_system_id().unwrap();
105 }
106
107 #[test]
108 #[cfg(target_os = "linux")]
109 fn can_get_system_id_on_linux() {
110 use std::path::Path;
111 const MODEL_FILENAME: &str = "/sys/firmware/devicetree/base/model";
112 const SN_FILENAME: &str = "/sys/firmware/devicetree/base/serial-number";
113 if !Path::new(MODEL_FILENAME).exists() || !Path::new(SN_FILENAME).exists() {
114 return; }
116
117 let _system_id = get_system_id().unwrap();
118 }
119}