running_process/broker/backend_lifecycle/
identity.rs1use std::convert::TryFrom;
10use std::fs;
11use std::io;
12use std::path::{Path, PathBuf};
13use std::time::{SystemTime, UNIX_EPOCH};
14
15use serde::{Deserialize, Deserializer, Serialize, Serializer};
16use sha2::{Digest, Sha256};
17
18use crate::broker::host_identity;
19use crate::broker::protocol::{self, CacheManifest, Endpoint};
20
21#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct DaemonProcess {
50 pub pid: u32,
52 pub exe_path: PathBuf,
54 pub exe_sha256: [u8; 32],
56 pub boot_id: String,
58 pub ipc_endpoint: Endpoint,
60 pub started_at_unix_ms: u64,
62 pub idle_timeout_secs: Option<u32>,
64}
65
66impl DaemonProcess {
67 pub fn current_process(
76 ipc_endpoint: Endpoint,
77 idle_timeout_secs: Option<u32>,
78 ) -> Result<Self, IdentityError> {
79 let exe_path = std::env::current_exe().map_err(IdentityError::CurrentExe)?;
80 let exe_sha256 = sha256_file(&exe_path)?;
81 Ok(Self {
82 pid: std::process::id(),
83 exe_path,
84 exe_sha256,
85 boot_id: host_identity::current().boot_id,
86 ipc_endpoint,
87 started_at_unix_ms: unix_now_ms(),
88 idle_timeout_secs,
89 })
90 }
91
92 pub fn to_proto(&self) -> protocol::DaemonProcess {
97 protocol::DaemonProcess {
98 pid: self.pid,
99 exe_path: self.exe_path.to_string_lossy().into_owned(),
100 exe_sha256: self.exe_sha256.to_vec(),
101 ipc_endpoint: Some(self.ipc_endpoint.clone()),
102 started_at_unix_ms: self.started_at_unix_ms,
103 boot_id: self.boot_id.clone(),
104 idle_timeout_secs: self.idle_timeout_secs,
105 }
106 }
107
108 pub fn from_manifest_current_daemon(
114 manifest: &CacheManifest,
115 ) -> Result<Option<Self>, IdentityError> {
116 manifest
117 .current_daemon
118 .clone()
119 .map(Self::try_from)
120 .transpose()
121 }
122}
123
124impl TryFrom<protocol::DaemonProcess> for DaemonProcess {
125 type Error = IdentityError;
126
127 fn try_from(value: protocol::DaemonProcess) -> Result<Self, Self::Error> {
128 let ipc_endpoint = value.ipc_endpoint.ok_or(IdentityError::MissingEndpoint)?;
129 let exe_sha256 =
130 vec_to_sha256(value.exe_sha256).map_err(IdentityError::InvalidSha256Length)?;
131 Ok(Self {
132 pid: value.pid,
133 exe_path: PathBuf::from(value.exe_path),
134 exe_sha256,
135 boot_id: value.boot_id,
136 ipc_endpoint,
137 started_at_unix_ms: value.started_at_unix_ms,
138 idle_timeout_secs: value.idle_timeout_secs,
139 })
140 }
141}
142
143impl From<&DaemonProcess> for protocol::DaemonProcess {
144 fn from(value: &DaemonProcess) -> Self {
145 value.to_proto()
146 }
147}
148
149impl Serialize for DaemonProcess {
150 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
151 where
152 S: Serializer,
153 {
154 DaemonProcessSerde::from(self).serialize(serializer)
155 }
156}
157
158impl<'de> Deserialize<'de> for DaemonProcess {
159 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
160 where
161 D: Deserializer<'de>,
162 {
163 let value = DaemonProcessSerde::deserialize(deserializer)?;
164 Ok(Self {
165 pid: value.pid,
166 exe_path: value.exe_path,
167 exe_sha256: value.exe_sha256,
168 boot_id: value.boot_id,
169 ipc_endpoint: value.ipc_endpoint.into(),
170 started_at_unix_ms: value.started_at_unix_ms,
171 idle_timeout_secs: value.idle_timeout_secs,
172 })
173 }
174}
175
176#[derive(Debug, thiserror::Error)]
178pub enum IdentityError {
179 #[error("daemon process is missing ipc_endpoint")]
181 MissingEndpoint,
182 #[error("daemon process exe_sha256 must be 32 bytes, got {0}")]
184 InvalidSha256Length(usize),
185 #[error("failed to resolve current executable: {0}")]
187 CurrentExe(io::Error),
188 #[error("failed to hash executable: {0}")]
190 Io(#[from] io::Error),
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize)]
194struct DaemonProcessSerde {
195 pid: u32,
196 exe_path: PathBuf,
197 exe_sha256: [u8; 32],
198 boot_id: String,
199 ipc_endpoint: EndpointSerde,
200 started_at_unix_ms: u64,
201 idle_timeout_secs: Option<u32>,
202}
203
204impl From<&DaemonProcess> for DaemonProcessSerde {
205 fn from(value: &DaemonProcess) -> Self {
206 Self {
207 pid: value.pid,
208 exe_path: value.exe_path.clone(),
209 exe_sha256: value.exe_sha256,
210 boot_id: value.boot_id.clone(),
211 ipc_endpoint: EndpointSerde::from(&value.ipc_endpoint),
212 started_at_unix_ms: value.started_at_unix_ms,
213 idle_timeout_secs: value.idle_timeout_secs,
214 }
215 }
216}
217
218#[derive(Debug, Clone, Serialize, Deserialize)]
219struct EndpointSerde {
220 namespace_id: String,
221 path: String,
222}
223
224impl From<&Endpoint> for EndpointSerde {
225 fn from(value: &Endpoint) -> Self {
226 Self {
227 namespace_id: value.namespace_id.clone(),
228 path: value.path.clone(),
229 }
230 }
231}
232
233impl From<EndpointSerde> for Endpoint {
234 fn from(value: EndpointSerde) -> Self {
235 Endpoint {
236 namespace_id: value.namespace_id,
237 path: value.path,
238 }
239 }
240}
241
242pub(crate) fn sha256_file(path: &Path) -> Result<[u8; 32], io::Error> {
243 let bytes = fs::read(path)?;
244 let digest = Sha256::digest(&bytes);
245 let mut out = [0_u8; 32];
246 out.copy_from_slice(&digest);
247 Ok(out)
248}
249
250fn vec_to_sha256(bytes: Vec<u8>) -> Result<[u8; 32], usize> {
251 let len = bytes.len();
252 let Ok(out) = bytes.try_into() else {
253 return Err(len);
254 };
255 Ok(out)
256}
257
258fn unix_now_ms() -> u64 {
259 SystemTime::now()
260 .duration_since(UNIX_EPOCH)
261 .map(|duration| duration.as_millis() as u64)
262 .unwrap_or(0)
263}