use crate::error::IoTError;
use find_folder::Search;
use log::debug;
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use std::env;
use std::fs::File;
pub const SHADOWS_NUM: usize = 2; pub const CONFIG_DIRNAME: &str = "device-iot.config";
pub const YAML_FILENAME: &str = "config.yaml";
pub const CERTS_SUBDIRNAME: &str = "certs";
pub const IOTREGISTRATIONSTATUS_INITIAL: &str = "INITIAL";
pub const IOTREGISTRATIONSTATUS_REGISTERED: &str = "REGISTERED";
pub const IOTREGISTRATIONSTATUS_REGISTERED_SEPARATOR: &str = "REGISTERED:";
pub const IOTREGISTRATIONSTATUS_CERTIFICATE_ROTATION_REQUESTED: &str =
"CERTIFICATE_ROTATION_REQUESTED";
pub const IOTREGISTRATIONSTATUS_CERTIFICATE_RECEIVED: &str = "CERTIFICATE_RECEIVED";
pub const IOTREGISTRATIONSTATUS_CSR_ERROR: &str = "CSR_ERROR";
pub const IOTREGISTRATIONSTATUS_REGISTRATION_ERROR: &str = "REGISTRATION_ERROR";
pub const IOTREGISTRATIONSTATUS_UNKNOWN_IOTREGISTRATIONSTATUS: &str =
"UNKNOWN_IotRegistrationStatus";
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum IotRegistrationStatus {
Initial,
CertReceived,
Registered(String),
CsrError,
RegistrationError,
CertificateRotationRequested,
Unknown,
}
impl std::fmt::Display for IotRegistrationStatus {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
IotRegistrationStatus::Initial => write!(f, "{}", IOTREGISTRATIONSTATUS_INITIAL),
IotRegistrationStatus::CertReceived => write!(f, "CERTIFICATE_RECEIVED"),
IotRegistrationStatus::Registered(t) => {
write!(f, "{}:{}", IOTREGISTRATIONSTATUS_REGISTERED, t)
}
IotRegistrationStatus::CsrError => write!(f, "CSR_ERROR"),
IotRegistrationStatus::RegistrationError => write!(f, "REGISTRATION_ERROR"),
IotRegistrationStatus::CertificateRotationRequested => write!(
f,
"{}",
IOTREGISTRATIONSTATUS_CERTIFICATE_ROTATION_REQUESTED
),
IotRegistrationStatus::Unknown => write!(f, "UNKNOWN_IotRegistrationStatus"),
}
}
}
impl std::str::FromStr for IotRegistrationStatus {
type Err = IoTError;
fn from_str(s: &str) -> ::core::result::Result<Self, Self::Err> {
let ss = s.to_string();
if ss.starts_with(IOTREGISTRATIONSTATUS_REGISTERED) {
match ss.strip_prefix(IOTREGISTRATIONSTATUS_REGISTERED_SEPARATOR) {
Some(thing_name) => ::core::result::Result::Ok(IotRegistrationStatus::Registered(
thing_name.to_string(),
)),
None => ::core::result::Result::Err(IoTError::IotStatusParsingError),
}
} else {
match s {
IOTREGISTRATIONSTATUS_INITIAL => {
::core::result::Result::Ok(IotRegistrationStatus::Initial)
}
IOTREGISTRATIONSTATUS_CERTIFICATE_ROTATION_REQUESTED => {
::core::result::Result::Ok(IotRegistrationStatus::CertificateRotationRequested)
}
IOTREGISTRATIONSTATUS_CERTIFICATE_RECEIVED => {
::core::result::Result::Ok(IotRegistrationStatus::CertReceived)
}
IOTREGISTRATIONSTATUS_CSR_ERROR => {
::core::result::Result::Ok(IotRegistrationStatus::CsrError)
}
IOTREGISTRATIONSTATUS_REGISTRATION_ERROR => {
::core::result::Result::Ok(IotRegistrationStatus::RegistrationError)
}
IOTREGISTRATIONSTATUS_UNKNOWN_IOTREGISTRATIONSTATUS => {
::core::result::Result::Ok(IotRegistrationStatus::Unknown)
}
_ => ::core::result::Result::Err(IoTError::IotStatusParsingError),
}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub struct DeviceConfig {
pub device_topic_prefix: String,
pub shadow_name: String,
pub client_id: String,
pub endpoint: String,
pub port: u16,
pub username: String,
pub password: String,
pub spec_version: String,
pub privacy: bool,
pub rudi_gtin: String,
pub rudi_ref: String,
pub instrument_type: String,
pub instrument_name: String,
pub instrument_serial_number: String,
pub source_id_type: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub struct IoTConfig {
pub iot_topic_prefix: String,
pub shadow_name: String,
pub client_registration_status: String,
pub client_id: String,
pub endpoint: String,
pub port: u16,
pub ca_path: String,
pub client_cert_path: String,
pub client_priv_key_path: String,
pub client_pub_key_path: String,
pub claim_cert_path: String,
pub claim_priv_key_path: String,
pub claim_pub_key_path: String,
pub provisioning_template_name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub struct Config {
pub device: DeviceConfig,
pub iot: IoTConfig,
}
fn set_abs_paths(config: &mut Config, conf_path_str: String) {
config.iot.ca_path = format!(
"{}/{}/{}",
conf_path_str, CERTS_SUBDIRNAME, config.iot.ca_path
);
config.iot.client_cert_path = format!(
"{}/{}/{}",
conf_path_str, CERTS_SUBDIRNAME, config.iot.client_cert_path
);
config.iot.client_priv_key_path = format!(
"{}/{}/{}",
conf_path_str, CERTS_SUBDIRNAME, config.iot.client_priv_key_path
);
config.iot.client_pub_key_path = format!(
"{}/{}/{}",
conf_path_str, CERTS_SUBDIRNAME, config.iot.client_pub_key_path
);
config.iot.claim_cert_path = format!(
"{}/{}/{}",
conf_path_str, CERTS_SUBDIRNAME, config.iot.claim_cert_path
);
config.iot.claim_priv_key_path = format!(
"{}/{}/{}",
conf_path_str, CERTS_SUBDIRNAME, config.iot.claim_priv_key_path
);
config.iot.claim_pub_key_path = format!(
"{}/{}/{}",
conf_path_str, CERTS_SUBDIRNAME, config.iot.claim_pub_key_path
);
}
impl Config {
pub fn set_abs_path_client_cert(&mut self) {
self.iot.client_cert_path = format!(
"{}/{}/{}",
CONFIG_LOCATION.as_path().display().to_string(),
CERTS_SUBDIRNAME,
self.iot.client_cert_path
);
}
pub fn get_config_from_yaml() -> Result<Config, IoTError> {
let conf_path_str = CONFIG_LOCATION.as_path().display().to_string();
let config_yaml_path = format!("{}/{}", conf_path_str, YAML_FILENAME);
debug!("CONFIG_PATH_YAML_FILENAME -- GET: {}", config_yaml_path);
let mut config: Config = serde_yaml::from_reader(&File::open(
std::path::Path::new(&config_yaml_path).to_path_buf(),
)?)?;
set_abs_paths(&mut config, conf_path_str);
Ok(config)
}
pub fn store_config_to_yaml(&mut self) -> Result<(), IoTError> {
if let Some(p) = self.iot.ca_path.rsplit_once('/') {
self.iot.ca_path = String::from(p.1)
};
if let Some(p) = self.iot.client_cert_path.rsplit_once('/') {
self.iot.client_cert_path = String::from(p.1)
};
if let Some(p) = self.iot.client_priv_key_path.rsplit_once('/') {
self.iot.client_priv_key_path = String::from(p.1)
};
if let Some(p) = self.iot.client_pub_key_path.rsplit_once('/') {
self.iot.client_pub_key_path = String::from(p.1)
};
if let Some(p) = self.iot.claim_cert_path.rsplit_once('/') {
self.iot.claim_cert_path = String::from(p.1)
};
if let Some(p) = self.iot.claim_priv_key_path.rsplit_once('/') {
self.iot.claim_priv_key_path = String::from(p.1)
};
if let Some(p) = self.iot.claim_pub_key_path.rsplit_once('/') {
self.iot.claim_pub_key_path = String::from(p.1)
};
let config_yaml_path = format!(
"{}/{}",
CONFIG_LOCATION.as_path().display().to_string(),
YAML_FILENAME
);
debug!(
"\nCONFIG_PATH_YAML_FILENAME -- STORE: {}\n",
config_yaml_path
);
serde_yaml::to_writer(
&File::create(std::path::Path::new(&config_yaml_path).to_path_buf())?,
&self,
)?;
Ok(())
}
}
pub static CONFIG_LOCATION: Lazy<std::path::PathBuf> = Lazy::new(|| {
let mut exe_folder = env::current_exe().unwrap();
exe_folder.pop(); Search::ParentsThenKids(5, 5)
.of(exe_folder)
.for_folder(CONFIG_DIRNAME)
.expect("Config directory not found")
});
pub fn build_thing_name(config: &Config) -> String {
format!(
"{}_{}",
config.device.instrument_type,
config.device.instrument_serial_number
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn store_and_get_config_yaml_test() {
let dev_config = DeviceConfig {
device_topic_prefix: String::from("SPDIF/X320/Poke/"),
shadow_name: String::from("c16a8_shadow"),
client_id: String::from("adapterClient"),
endpoint: String::from("127.0.0.1"),
port: 1883,
username: String::from("username"),
password: String::from("password"),
spec_version: String::from("1.0"),
privacy: false,
rudi_gtin: String::from("8724447281187"),
rudi_ref: String::from("9818575112"),
instrument_type: String::from("16A8"),
instrument_name: String::from("Friendly_Name"),
instrument_serial_number: String::from("99999"),
source_id_type: String::from("CONFIG"),
};
let iot_config = IoTConfig {
iot_topic_prefix: String::from("SPDIF/X320/16A8/"),
shadow_name: String::from("iot_shadow"),
client_registration_status: String::from("INITIAL"),
client_id: String::from("16A8_99998"),
endpoint: String::from("ENDPOINTID-ats.iot.eu-central-1.amazonaws.com"),
port: 8883,
ca_path: String::from("AmazonRootCA1.pem"),
client_cert_path: String::from("IotCertificate.pem"),
client_priv_key_path: String::from("IotPrivateKey.pem"),
client_pub_key_path: String::from("IotPubKey.pem"),
claim_cert_path: String::from("ClaimCertificate.pem"),
claim_priv_key_path: String::from("ClaimPrivateKey.pem"),
claim_pub_key_path: String::from("ClaimPubKey.pem"),
provisioning_template_name: String::from("iot-16A8-prov-templ"),
};
let mut config = Config {
device: dev_config,
iot: iot_config,
};
config
.store_config_to_yaml()
.expect("problem storing \"config.yaml\"");
let config_read = Config::get_config_from_yaml().expect("Problem reading \"config.yaml\"");
assert_eq!(config.device, config_read.device);
assert_eq!(config.iot.endpoint, config_read.iot.endpoint);
let config_certs_capath = format!(
"{}/certs/{}",
CONFIG_LOCATION.as_path().display().to_string(),
config.iot.ca_path
);
assert_eq!(config_certs_capath, config_read.iot.ca_path);
}
}