2024-07-01 19:10:45 +00:00
|
|
|
use crate::app_config::AppConfig;
|
2024-07-03 19:32:32 +00:00
|
|
|
use crate::crypto::pki;
|
2024-08-27 20:32:22 +00:00
|
|
|
use crate::devices::device::{
|
|
|
|
Device, DeviceGeneralInfo, DeviceId, DeviceInfo, DeviceRelay, DeviceRelayID,
|
|
|
|
};
|
2024-07-02 20:55:51 +00:00
|
|
|
use crate::utils::time_utils::time_secs;
|
2024-07-17 16:31:57 +00:00
|
|
|
use openssl::x509::{X509Req, X509};
|
2024-07-01 19:10:45 +00:00
|
|
|
use std::collections::HashMap;
|
|
|
|
|
2024-07-02 20:55:51 +00:00
|
|
|
#[derive(thiserror::Error, Debug)]
|
|
|
|
pub enum DevicesListError {
|
|
|
|
#[error("Enrollment failed: a device with the same ID was already registered!")]
|
|
|
|
EnrollFailedDeviceAlreadyExists,
|
|
|
|
#[error("Persist device config failed: the configuration of the device was not found!")]
|
|
|
|
PersistFailedDeviceNotFound,
|
2024-07-03 19:32:32 +00:00
|
|
|
#[error("Validated device failed: the device does not exists!")]
|
|
|
|
ValidateDeviceFailedDeviceNotFound,
|
|
|
|
#[error("Validated device failed: the device is already validated!")]
|
|
|
|
ValidateDeviceFailedDeviceAlreadyValidated,
|
2024-07-22 20:19:48 +00:00
|
|
|
#[error("Update device failed: the device does not exists!")]
|
|
|
|
UpdateDeviceFailedDeviceNotFound,
|
2024-07-17 16:31:57 +00:00
|
|
|
#[error("Requested device was not found")]
|
|
|
|
DeviceNotFound,
|
|
|
|
#[error("Requested device is not validated")]
|
|
|
|
DeviceNotValidated,
|
2024-08-31 18:46:02 +00:00
|
|
|
#[error("Failed to delete device: {0}")]
|
|
|
|
DeleteDeviceFailed(&'static str),
|
2024-08-31 18:26:16 +00:00
|
|
|
#[error("Failed to update relay configuration: {0}")]
|
|
|
|
UpdateRelayFailed(&'static str),
|
2024-08-31 18:00:40 +00:00
|
|
|
#[error("Failed to delete relay: {0}")]
|
|
|
|
DeleteRelayFailed(&'static str),
|
2024-07-02 20:55:51 +00:00
|
|
|
}
|
|
|
|
|
2024-07-01 19:10:45 +00:00
|
|
|
pub struct DevicesList(HashMap<DeviceId, Device>);
|
|
|
|
|
|
|
|
impl DevicesList {
|
|
|
|
/// Load the list of devices. This method should be called only once during the whole execution
|
|
|
|
/// of the program
|
|
|
|
pub fn load() -> anyhow::Result<Self> {
|
|
|
|
let mut list = Self(HashMap::new());
|
|
|
|
|
|
|
|
for f in std::fs::read_dir(AppConfig::get().devices_config_path())? {
|
|
|
|
let f = f?.file_name();
|
|
|
|
let f = f.to_string_lossy();
|
|
|
|
|
|
|
|
let dev_id = match f.strip_suffix(".conf") {
|
|
|
|
Some(s) => DeviceId(s.to_string()),
|
|
|
|
|
|
|
|
// This is not a device configuration file
|
|
|
|
None => continue,
|
|
|
|
};
|
|
|
|
|
|
|
|
let device_conf = std::fs::read(AppConfig::get().device_config_path(&dev_id))?;
|
|
|
|
|
|
|
|
list.0.insert(dev_id, serde_json::from_slice(&device_conf)?);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(list)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Check if a device with a given id exists or not
|
|
|
|
pub fn exists(&self, id: &DeviceId) -> bool {
|
|
|
|
self.0.contains_key(id)
|
|
|
|
}
|
2024-07-02 20:55:51 +00:00
|
|
|
|
|
|
|
/// Enroll a new device
|
|
|
|
pub fn enroll(
|
|
|
|
&mut self,
|
|
|
|
id: &DeviceId,
|
|
|
|
info: &DeviceInfo,
|
|
|
|
csr: &X509Req,
|
|
|
|
) -> anyhow::Result<()> {
|
|
|
|
if self.exists(id) {
|
|
|
|
return Err(DevicesListError::EnrollFailedDeviceAlreadyExists.into());
|
|
|
|
}
|
|
|
|
|
|
|
|
let device = Device {
|
|
|
|
id: id.clone(),
|
|
|
|
info: info.clone(),
|
|
|
|
time_create: time_secs(),
|
|
|
|
time_update: time_secs(),
|
|
|
|
name: id.0.to_string(),
|
|
|
|
description: "".to_string(),
|
|
|
|
validated: false,
|
|
|
|
enabled: false,
|
|
|
|
relays: vec![],
|
2024-10-05 14:26:07 +00:00
|
|
|
desired_version: None,
|
2024-07-02 20:55:51 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// First, write CSR
|
|
|
|
std::fs::write(AppConfig::get().device_csr_path(id), csr.to_pem()?)?;
|
|
|
|
|
|
|
|
self.0.insert(id.clone(), device);
|
|
|
|
self.persist_dev_config(id)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Persist a device configuration on the filesystem
|
|
|
|
fn persist_dev_config(&self, id: &DeviceId) -> anyhow::Result<()> {
|
|
|
|
let dev = self
|
|
|
|
.0
|
|
|
|
.get(id)
|
2024-07-03 17:17:47 +00:00
|
|
|
.ok_or(DevicesListError::PersistFailedDeviceNotFound)?;
|
2024-07-02 20:55:51 +00:00
|
|
|
|
|
|
|
std::fs::write(
|
|
|
|
AppConfig::get().device_config_path(id),
|
|
|
|
serde_json::to_string_pretty(dev)?,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2024-07-03 17:17:47 +00:00
|
|
|
|
|
|
|
/// Get a copy of the full list of devices
|
|
|
|
pub fn full_list(&self) -> Vec<Device> {
|
|
|
|
self.0.clone().into_values().collect()
|
|
|
|
}
|
2024-07-03 19:10:15 +00:00
|
|
|
|
2024-09-26 20:51:43 +00:00
|
|
|
/// Get a reference on the full list of devices
|
|
|
|
pub fn full_list_ref(&self) -> Vec<&Device> {
|
|
|
|
self.0.values().collect()
|
|
|
|
}
|
|
|
|
|
2024-07-03 20:05:19 +00:00
|
|
|
/// Get the information about a single device
|
|
|
|
pub fn get_single(&self, id: &DeviceId) -> Option<Device> {
|
|
|
|
self.0.get(id).cloned()
|
|
|
|
}
|
|
|
|
|
2024-07-03 19:32:32 +00:00
|
|
|
/// Validate a device
|
|
|
|
pub fn validate(&mut self, id: &DeviceId) -> anyhow::Result<()> {
|
|
|
|
let dev = self
|
|
|
|
.0
|
|
|
|
.get_mut(id)
|
|
|
|
.ok_or(DevicesListError::ValidateDeviceFailedDeviceNotFound)?;
|
|
|
|
|
|
|
|
if dev.validated {
|
|
|
|
return Err(DevicesListError::ValidateDeviceFailedDeviceAlreadyValidated.into());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Issue certificate
|
|
|
|
let csr = X509Req::from_pem(&std::fs::read(AppConfig::get().device_csr_path(id))?)?;
|
|
|
|
let cert = pki::gen_certificate_for_device(&csr)?;
|
|
|
|
std::fs::write(AppConfig::get().device_cert_path(id), cert)?;
|
|
|
|
|
|
|
|
// Mark device as validated
|
|
|
|
dev.validated = true;
|
|
|
|
self.persist_dev_config(id)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-07-22 20:19:48 +00:00
|
|
|
/// Update a device general information
|
2024-09-15 20:06:24 +00:00
|
|
|
pub fn synchronise_dev_info(
|
|
|
|
&mut self,
|
|
|
|
id: &DeviceId,
|
|
|
|
general_info: DeviceInfo,
|
|
|
|
) -> anyhow::Result<()> {
|
|
|
|
let dev = self
|
|
|
|
.0
|
|
|
|
.get_mut(id)
|
|
|
|
.ok_or(DevicesListError::UpdateDeviceFailedDeviceNotFound)?;
|
|
|
|
|
|
|
|
dev.info = general_info;
|
|
|
|
self.persist_dev_config(id)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Update a device general information
|
2024-07-22 20:19:48 +00:00
|
|
|
pub fn update_general_info(
|
|
|
|
&mut self,
|
|
|
|
id: &DeviceId,
|
|
|
|
general_info: DeviceGeneralInfo,
|
|
|
|
) -> anyhow::Result<()> {
|
|
|
|
let dev = self
|
|
|
|
.0
|
|
|
|
.get_mut(id)
|
|
|
|
.ok_or(DevicesListError::UpdateDeviceFailedDeviceNotFound)?;
|
|
|
|
|
|
|
|
dev.name = general_info.name;
|
|
|
|
dev.description = general_info.description;
|
|
|
|
dev.enabled = general_info.enabled;
|
|
|
|
dev.time_update = time_secs();
|
|
|
|
|
|
|
|
self.persist_dev_config(id)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-10-05 14:26:07 +00:00
|
|
|
/// Set a device desired version
|
|
|
|
pub fn set_desired_version(
|
|
|
|
&mut self,
|
|
|
|
id: &DeviceId,
|
|
|
|
version: Option<semver::Version>,
|
|
|
|
) -> anyhow::Result<()> {
|
|
|
|
let dev = self
|
|
|
|
.0
|
|
|
|
.get_mut(id)
|
|
|
|
.ok_or(DevicesListError::UpdateDeviceFailedDeviceNotFound)?;
|
|
|
|
|
|
|
|
dev.desired_version = version;
|
|
|
|
|
|
|
|
self.persist_dev_config(id)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-07-17 16:31:57 +00:00
|
|
|
/// Get single certificate information
|
|
|
|
fn get_cert(&self, id: &DeviceId) -> anyhow::Result<X509> {
|
|
|
|
let dev = self
|
|
|
|
.get_single(id)
|
|
|
|
.ok_or(DevicesListError::DeviceNotFound)?;
|
|
|
|
if !dev.validated {
|
|
|
|
return Err(DevicesListError::DeviceNotValidated.into());
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(X509::from_pem(&std::fs::read(
|
|
|
|
AppConfig::get().device_cert_path(id),
|
|
|
|
)?)?)
|
|
|
|
}
|
|
|
|
|
2024-07-03 19:10:15 +00:00
|
|
|
/// Delete a device
|
|
|
|
pub fn delete(&mut self, id: &DeviceId) -> anyhow::Result<()> {
|
2024-08-31 18:46:02 +00:00
|
|
|
// Check for conflicts
|
|
|
|
let device = self
|
|
|
|
.get_single(id)
|
|
|
|
.ok_or(DevicesListError::DeleteDeviceFailed("Device not found!"))?;
|
|
|
|
for r in &device.relays {
|
|
|
|
if !self.relay_get_direct_dependencies(r.id).is_empty() {
|
|
|
|
return Err(DevicesListError::DeleteDeviceFailed(
|
|
|
|
"A relay of this device is required by another relay!",
|
|
|
|
)
|
|
|
|
.into());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-03 19:10:15 +00:00
|
|
|
let crt_path = AppConfig::get().device_cert_path(id);
|
|
|
|
if crt_path.is_file() {
|
2024-07-17 16:31:57 +00:00
|
|
|
let cert = self.get_cert(id)?;
|
|
|
|
pki::revoke_device_cert(&cert)?;
|
2024-07-03 19:10:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let csr_path = AppConfig::get().device_csr_path(id);
|
|
|
|
if csr_path.is_file() {
|
|
|
|
std::fs::remove_file(&csr_path)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
let conf_path = AppConfig::get().device_config_path(id);
|
|
|
|
if conf_path.is_file() {
|
|
|
|
std::fs::remove_file(&conf_path)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.0.remove(id);
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2024-07-24 21:35:58 +00:00
|
|
|
|
|
|
|
/// Get the full list of relays
|
2024-08-27 20:32:22 +00:00
|
|
|
pub fn relays_list(&self) -> Vec<DeviceRelay> {
|
2024-07-24 21:35:58 +00:00
|
|
|
self.0
|
|
|
|
.iter()
|
|
|
|
.flat_map(|(_id, d)| d.relays.clone())
|
|
|
|
.collect()
|
|
|
|
}
|
2024-08-27 20:32:22 +00:00
|
|
|
|
|
|
|
/// Create a new relay
|
|
|
|
pub fn relay_create(&mut self, dev_id: &DeviceId, relay: DeviceRelay) -> anyhow::Result<()> {
|
|
|
|
let dev = self
|
|
|
|
.0
|
|
|
|
.get_mut(dev_id)
|
|
|
|
.ok_or(DevicesListError::DeviceNotFound)?;
|
|
|
|
|
|
|
|
dev.relays.push(relay);
|
|
|
|
|
|
|
|
self.persist_dev_config(dev_id)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get a single relay
|
|
|
|
pub fn relay_get_single(&self, relay_id: DeviceRelayID) -> Option<DeviceRelay> {
|
|
|
|
self.relays_list().into_iter().find(|i| i.id == relay_id)
|
|
|
|
}
|
2024-08-31 18:00:40 +00:00
|
|
|
|
2024-08-31 18:26:16 +00:00
|
|
|
/// Get a mutable reference on a single relay
|
|
|
|
pub fn relay_get_single_mut(&mut self, relay_id: DeviceRelayID) -> Option<&mut DeviceRelay> {
|
|
|
|
self.0
|
|
|
|
.iter_mut()
|
|
|
|
.find(|d| d.1.relays.iter().any(|r| r.id == relay_id))?
|
|
|
|
.1
|
|
|
|
.relays
|
|
|
|
.iter_mut()
|
|
|
|
.find(|r| r.id == relay_id)
|
|
|
|
}
|
|
|
|
|
2024-08-31 18:00:40 +00:00
|
|
|
/// Get the device hosting a relay
|
2024-08-31 18:26:16 +00:00
|
|
|
pub fn relay_get_device(&mut self, relay_id: DeviceRelayID) -> Option<&mut Device> {
|
2024-08-31 18:00:40 +00:00
|
|
|
self.0
|
2024-08-31 18:26:16 +00:00
|
|
|
.iter_mut()
|
2024-08-31 18:00:40 +00:00
|
|
|
.find(|r| r.1.relays.iter().any(|r| r.id == relay_id))
|
2024-08-31 18:26:16 +00:00
|
|
|
.map(|d| d.1)
|
2024-08-31 18:00:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Get all the relays that depends directly on a relay
|
|
|
|
pub fn relay_get_direct_dependencies(&self, relay_id: DeviceRelayID) -> Vec<DeviceRelay> {
|
|
|
|
self.relays_list()
|
|
|
|
.into_iter()
|
|
|
|
.filter(|d| d.depends_on.contains(&relay_id))
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
2024-08-31 18:26:16 +00:00
|
|
|
/// Update a relay configuration
|
|
|
|
pub fn relay_update(&mut self, relay: DeviceRelay) -> anyhow::Result<()> {
|
|
|
|
let device = self
|
|
|
|
.relay_get_device(relay.id)
|
|
|
|
.ok_or(DevicesListError::UpdateRelayFailed(
|
|
|
|
"Relay does not exists!",
|
|
|
|
))?;
|
|
|
|
|
|
|
|
let idx = device.relays.iter().position(|r| r.id == relay.id).ok_or(
|
|
|
|
DevicesListError::UpdateRelayFailed("Relay index not found!"),
|
|
|
|
)?;
|
|
|
|
|
|
|
|
// Update the relay configuration
|
|
|
|
device.relays[idx] = relay;
|
|
|
|
let device_id = device.id.clone();
|
|
|
|
|
|
|
|
self.persist_dev_config(&device_id)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-08-31 18:00:40 +00:00
|
|
|
/// Delete a relay
|
|
|
|
pub fn relay_delete(&mut self, relay_id: DeviceRelayID) -> anyhow::Result<()> {
|
|
|
|
if !self.relay_get_direct_dependencies(relay_id).is_empty() {
|
|
|
|
return Err(DevicesListError::DeleteRelayFailed(
|
|
|
|
"At least one other relay depend on this relay!",
|
|
|
|
)
|
|
|
|
.into());
|
|
|
|
}
|
|
|
|
|
2024-09-17 20:42:24 +00:00
|
|
|
// Delete relay energy information
|
|
|
|
let stats_dir = AppConfig::get().relay_runtime_stats_dir(relay_id);
|
|
|
|
if stats_dir.is_dir() {
|
|
|
|
std::fs::remove_dir_all(stats_dir)?;
|
|
|
|
}
|
|
|
|
|
2024-08-31 18:00:40 +00:00
|
|
|
// Delete the relay
|
|
|
|
let device = self
|
|
|
|
.relay_get_device(relay_id)
|
|
|
|
.ok_or(DevicesListError::DeleteRelayFailed(
|
|
|
|
"Relay does not exists!",
|
|
|
|
))?;
|
|
|
|
|
2024-08-31 18:26:16 +00:00
|
|
|
device.relays.retain(|r| r.id != relay_id);
|
2024-08-31 18:00:40 +00:00
|
|
|
|
2024-08-31 18:26:16 +00:00
|
|
|
let device_id = device.id.clone();
|
|
|
|
self.persist_dev_config(&device_id)?;
|
2024-08-31 18:00:40 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2024-07-01 19:10:45 +00:00
|
|
|
}
|