use std::convert::TryFrom;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use sha2::{Digest, Sha256};
use crate::broker::host_identity;
use crate::broker::protocol::{self, CacheManifest, Endpoint};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DaemonProcess {
pub pid: u32,
pub exe_path: PathBuf,
pub exe_sha256: [u8; 32],
pub boot_id: String,
pub ipc_endpoint: Endpoint,
pub started_at_unix_ms: u64,
pub idle_timeout_secs: Option<u32>,
}
impl DaemonProcess {
pub fn current_process(
ipc_endpoint: Endpoint,
idle_timeout_secs: Option<u32>,
) -> Result<Self, IdentityError> {
let exe_path = std::env::current_exe().map_err(IdentityError::CurrentExe)?;
let exe_sha256 = sha256_file(&exe_path)?;
Ok(Self {
pid: std::process::id(),
exe_path,
exe_sha256,
boot_id: host_identity::current().boot_id,
ipc_endpoint,
started_at_unix_ms: unix_now_ms(),
idle_timeout_secs,
})
}
pub fn to_proto(&self) -> protocol::DaemonProcess {
protocol::DaemonProcess {
pid: self.pid,
exe_path: self.exe_path.to_string_lossy().into_owned(),
exe_sha256: self.exe_sha256.to_vec(),
ipc_endpoint: Some(self.ipc_endpoint.clone()),
started_at_unix_ms: self.started_at_unix_ms,
boot_id: self.boot_id.clone(),
idle_timeout_secs: self.idle_timeout_secs,
}
}
pub fn from_manifest_current_daemon(
manifest: &CacheManifest,
) -> Result<Option<Self>, IdentityError> {
manifest
.current_daemon
.clone()
.map(Self::try_from)
.transpose()
}
}
impl TryFrom<protocol::DaemonProcess> for DaemonProcess {
type Error = IdentityError;
fn try_from(value: protocol::DaemonProcess) -> Result<Self, Self::Error> {
let ipc_endpoint = value.ipc_endpoint.ok_or(IdentityError::MissingEndpoint)?;
let exe_sha256 =
vec_to_sha256(value.exe_sha256).map_err(IdentityError::InvalidSha256Length)?;
Ok(Self {
pid: value.pid,
exe_path: PathBuf::from(value.exe_path),
exe_sha256,
boot_id: value.boot_id,
ipc_endpoint,
started_at_unix_ms: value.started_at_unix_ms,
idle_timeout_secs: value.idle_timeout_secs,
})
}
}
impl From<&DaemonProcess> for protocol::DaemonProcess {
fn from(value: &DaemonProcess) -> Self {
value.to_proto()
}
}
impl Serialize for DaemonProcess {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
DaemonProcessSerde::from(self).serialize(serializer)
}
}
impl<'de> Deserialize<'de> for DaemonProcess {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = DaemonProcessSerde::deserialize(deserializer)?;
Ok(Self {
pid: value.pid,
exe_path: value.exe_path,
exe_sha256: value.exe_sha256,
boot_id: value.boot_id,
ipc_endpoint: value.ipc_endpoint.into(),
started_at_unix_ms: value.started_at_unix_ms,
idle_timeout_secs: value.idle_timeout_secs,
})
}
}
#[derive(Debug, thiserror::Error)]
pub enum IdentityError {
#[error("daemon process is missing ipc_endpoint")]
MissingEndpoint,
#[error("daemon process exe_sha256 must be 32 bytes, got {0}")]
InvalidSha256Length(usize),
#[error("failed to resolve current executable: {0}")]
CurrentExe(io::Error),
#[error("failed to hash executable: {0}")]
Io(#[from] io::Error),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct DaemonProcessSerde {
pid: u32,
exe_path: PathBuf,
exe_sha256: [u8; 32],
boot_id: String,
ipc_endpoint: EndpointSerde,
started_at_unix_ms: u64,
idle_timeout_secs: Option<u32>,
}
impl From<&DaemonProcess> for DaemonProcessSerde {
fn from(value: &DaemonProcess) -> Self {
Self {
pid: value.pid,
exe_path: value.exe_path.clone(),
exe_sha256: value.exe_sha256,
boot_id: value.boot_id.clone(),
ipc_endpoint: EndpointSerde::from(&value.ipc_endpoint),
started_at_unix_ms: value.started_at_unix_ms,
idle_timeout_secs: value.idle_timeout_secs,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct EndpointSerde {
namespace_id: String,
path: String,
}
impl From<&Endpoint> for EndpointSerde {
fn from(value: &Endpoint) -> Self {
Self {
namespace_id: value.namespace_id.clone(),
path: value.path.clone(),
}
}
}
impl From<EndpointSerde> for Endpoint {
fn from(value: EndpointSerde) -> Self {
Endpoint {
namespace_id: value.namespace_id,
path: value.path,
}
}
}
pub(crate) fn sha256_file(path: &Path) -> Result<[u8; 32], io::Error> {
let bytes = fs::read(path)?;
let digest = Sha256::digest(&bytes);
let mut out = [0_u8; 32];
out.copy_from_slice(&digest);
Ok(out)
}
fn vec_to_sha256(bytes: Vec<u8>) -> Result<[u8; 32], usize> {
let len = bytes.len();
let Ok(out) = bytes.try_into() else {
return Err(len);
};
Ok(out)
}
fn unix_now_ms() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|duration| duration.as_millis() as u64)
.unwrap_or(0)
}