use crate::app_config::AppConfig; use crate::crypto::pki; use crate::devices::device::{ Device, DeviceGeneralInfo, DeviceId, DeviceInfo, DeviceRelay, DeviceRelayID, }; use crate::utils::time_utils::time_secs; use openssl::x509::{X509Req, X509}; use std::collections::HashMap; #[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, #[error("Validated device failed: the device does not exists!")] ValidateDeviceFailedDeviceNotFound, #[error("Validated device failed: the device is already validated!")] ValidateDeviceFailedDeviceAlreadyValidated, #[error("Update device failed: the device does not exists!")] UpdateDeviceFailedDeviceNotFound, #[error("Requested device was not found")] DeviceNotFound, #[error("Requested device is not validated")] DeviceNotValidated, } pub struct DevicesList(HashMap); 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 { 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) } /// 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![], }; // 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) .ok_or(DevicesListError::PersistFailedDeviceNotFound)?; std::fs::write( AppConfig::get().device_config_path(id), serde_json::to_string_pretty(dev)?, )?; Ok(()) } /// Get a copy of the full list of devices pub fn full_list(&self) -> Vec { self.0.clone().into_values().collect() } /// Get the information about a single device pub fn get_single(&self, id: &DeviceId) -> Option { self.0.get(id).cloned() } /// 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(()) } /// Update a device general information 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(()) } /// Get single certificate information fn get_cert(&self, id: &DeviceId) -> anyhow::Result { 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), )?)?) } /// Delete a device pub fn delete(&mut self, id: &DeviceId) -> anyhow::Result<()> { let crt_path = AppConfig::get().device_cert_path(id); if crt_path.is_file() { let cert = self.get_cert(id)?; pki::revoke_device_cert(&cert)?; } 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(()) } /// Get the full list of relays pub fn relays_list(&self) -> Vec { self.0 .iter() .flat_map(|(_id, d)| d.relays.clone()) .collect() } /// 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 { self.relays_list().into_iter().find(|i| i.id == relay_id) } }