1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
use std::{
fs::OpenOptions,
io::Write,
path::{Path, PathBuf},
};
use serde::{Deserialize, Serialize};
use crate::{certs::Certs, get_local_dir};
const DEVICE_CONFIG_TOML: &str = "device_allowed.toml";
pub struct Device {
pub id: String,
pub allowed: Vec<String>,
dir: PathBuf,
}
#[derive(Serialize, Deserialize, Default)]
struct DeviceConfig {
allowed: Vec<String>,
}
impl Device {
/// Creates a new instance of the device.
///
/// This constructor initializes the device by setting up its configuration,
/// generating necessary certificates, and ensuring it is properly initialized.
/// It performs the following steps:
/// - Retrieves the local directory path.
/// - Initializes the configuration file if not already done.
/// - Generates the device certificate using provided certs.
/// - Computes a unique identifier for the device based on its key bytes.
/// - Loads allowed device IDs from the configuration directory.
///
/// # Arguments
///
/// * `certs` - A reference to the certificates required for initialization.
///
/// # Returns
///
/// This function returns a new instance of the device wrapped in a `Result`.
/// If any step fails, an error is returned with detailed information about what went wrong.
///
pub fn new(certs: &Certs) -> anyhow::Result<Self> {
let dir = get_local_dir()?;
Self::init_config(&dir)?;
certs.gen_device()?;
let id = sha256::digest(certs.get_device_key_bytes()?);
let allowed = Self::load_allowed_device_ids(&dir)?;
Ok(Self { id, allowed, dir })
}
/// Allows a device with the specified ID.
///
/// If the device is not already allowed, it will be added to the list and the configuration saved.
/// Returns `true` if the operation was successful and the device was added; otherwise returns `false`.
///
/// # Arguments
/// * `id`: The unique identifier of the device to allow.
///
pub fn allow(&self, id: String) -> anyhow::Result<bool> {
let mut config = Self::load_config(&self.dir)?;
if !config.allowed.contains(&id) {
config.allowed.push(id);
Self::save_config(&self.dir, &config)?;
Ok(true)
} else {
Ok(false)
}
}
/// Disables a device with the specified ID.
///
/// If the device is currently allowed, it will be removed from the list and the configuration saved.
/// Returns `true` if the operation was successful; otherwise returns `false`.
///
/// # Arguments
/// * `id`: The unique identifier of the device to disable.
///
pub fn disable(&self, id: String) -> anyhow::Result<bool> {
let mut config = Self::load_config(&self.dir)?;
if config.allowed.contains(&id) {
config.allowed.retain(|x| x != &id);
Self::save_config(&self.dir, &config)?;
Ok(true)
} else {
Ok(false)
}
}
/// Checks whether a device is allowed.
///
/// Returns `true` if the specified device ID is in the list of allowed devices; otherwise returns `false`.
///
/// # Arguments
/// * `id`: The unique identifier of the device to check.
///
pub fn is_allowed(&self, id: String) -> bool {
self.allowed.contains(&id)
}
// Loads a vector containing all allowed device IDs from the configuration file.
///
/// # Arguments
/// * `dir`: A reference to the directory where the configuration file resides.
///
fn load_allowed_device_ids(dir: &Path) -> anyhow::Result<Vec<String>> {
let config = Self::load_config(dir)?;
Ok(config.allowed)
}
/// Initializes the device configuration if it does not already exist in the specified directory.
///
/// If a configuration file is missing, this method creates one using default values and saves it to disk.
///
/// # Arguments
/// * `dir`: A reference to the directory where the configuration should be initialized.
///
fn init_config(dir: &Path) -> anyhow::Result<()> {
if !dir.join(DEVICE_CONFIG_TOML).as_path().exists() {
let config = DeviceConfig::default();
Self::save_config(dir, &config)?;
}
Ok(())
}
/// Loads the device configuration from a TOML file.
///
/// # Arguments
/// * `dir`: A reference to the directory where the configuration file is located.
///
fn load_config(dir: &Path) -> anyhow::Result<DeviceConfig> {
let toml_str = std::fs::read_to_string(dir.join(DEVICE_CONFIG_TOML))?;
toml::from_str(&toml_str).map_err(anyhow::Error::from)
}
/// Saves the device configuration to a TOML file.
///
/// # Arguments
/// * `dir`: A reference to the directory where the configuration should be saved.
/// * `config`: The device configuration object to serialize and save.
///
fn save_config(dir: &Path, config: &DeviceConfig) -> anyhow::Result<()> {
let mut config_file = OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(dir.join(DEVICE_CONFIG_TOML))
.map_err(|e| anyhow::anyhow!("Failed to create/open a device config file: {}", e))?;
let toml_str = toml::to_string_pretty(&config)?;
config_file.write_all(toml_str.as_bytes())?;
Ok(())
}
}