artisan_middleware 7.0.0

The main services of the artisan platform to allow communication and management of linux system services
Documentation
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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
use aes_gcm::{aead::consts::U12, aead::Aead, Aes256Gcm, Key, KeyInit, Nonce};
use dusa_collection_utils::{core::logger::LogLevel, core::types::stringy::Stringy, log};
use rand::RngExt;
use tokio::sync::Notify;

use dusa_collection_utils::core::errors::{ErrorArrayItem, Errors, UnifiedResult};
#[cfg(target_os = "linux")]
// use recs::{decrypt_raw, encrypt_raw, house_keeping, initialize};
use std::{
    sync::{
        atomic::{AtomicBool, Ordering},
        Arc,
    },
    time::Duration,
};
use tokio::time::sleep;

#[cfg(target_os = "linux")]
lazy_static::lazy_static! {
/// Indicates whether the legacy (RECS-based) encryption system has been initialized.
static ref initialized:  Arc<AtomicBool> = Arc::new(AtomicBool::new(false));

    /// Tracks if the cleaning loop used by the RECS system has been spawned.
    static ref cleaning_loop_initialized: Arc<AtomicBool> = Arc::new(AtomicBool::new(false));

    /// A `Notify` instance used to trigger a "cleaning" operation within RECS.
    static ref cleaning_call: Arc<Notify> = Arc::new(Notify::new());

    /// Indicates whether the encryption/decryption operations are currently "locked" while cleaning.
    static ref cleaning_lock: Arc<AtomicBool> = Arc::new(AtomicBool::new(false));
}

// region: Legacy Encryption/Decryption

/// Encrypts text data using the legacy RECS-based encryption system.
///
/// # Deprecation
/// Marked as **deprecated** since version 4.3.0.
/// Please use [`simple_encrypt`] instead if possible.
///
/// # Arguments
/// - `data`: The [`Stringy`] text to encrypt.
///
/// # Returns
/// - `Ok(Stringy)`: The encrypted data as a `Stringy`.
/// - `Err(ErrorArrayItem)`: An error if encryption fails.
///
/// # Example
/// ```rust
/// # use dusa_collection_utils::core::types::stringy::Stringy;
/// # use tokio::runtime::Runtime;
/// # use std::time::Duration;
/// # use artisan_middleware::encryption::encrypt_text;
/// # let rt = Runtime::new().unwrap();
/// # let text = Stringy::from("sensitive information");
/// # rt.block_on(async {
///     
///     #[allow(deprecated)]
///     match encrypt_text(text).await {
///         Ok(encrypted) => println!("Encrypted data: {}", encrypted),
///         Err(err) => eprintln!("Encryption failed: {}", err),
///     }
///
///  # });
/// ```
#[allow(deprecated)]
#[cfg(target_os = "linux")]
#[deprecated(
    since = "4.3.0",
    note = "Currently unstable. Use `simple_encrypt` if possible."
)]
pub async fn encrypt_text(data: Stringy) -> Result<Stringy, ErrorArrayItem> {
    let data_bytes = data.as_bytes().to_vec();
    let plain_bytes = encrypt_data(&data_bytes).await.uf_unwrap()?;

    let text = Stringy::from(String::from_utf8(plain_bytes)?);
    Ok(text)
}

/// Decrypts text data using the legacy RECS-based decryption system.
///
/// # Deprecation
/// Marked as **deprecated** since version 4.3.0.
/// Please use [`simple_decrypt`] instead if possible.
///
/// # Arguments
/// - `data`: The [`Stringy`] text to decrypt.
///
/// # Returns
/// - `Ok(Stringy)`: The decrypted data as a `Stringy`.
/// - `Err(ErrorArrayItem)`: An error if decryption fails.
///
/// # Example
/// ```rust
/// # use dusa_collection_utils::core::types::stringy::Stringy;
/// # use tokio::runtime::Runtime;
/// # use std::time::Duration;
/// # use artisan_middleware::encryption::decrypt_text;
/// # use artisan_middleware::encryption::encrypt_text;
/// # let rt = Runtime::new().unwrap();
/// # let text = Stringy::from("sensitive information");
/// # rt.block_on(async {
///
///     #[allow(deprecated)]
///     let encrypted = encrypt_text(text).await.unwrap();
///
///     #[allow(deprecated)]
///     match decrypt_text(encrypted).await {
///         Ok(decrypted) => println!("Decrypted data: {}", decrypted),
///         Err(err) => eprintln!("Decryption failed: {}", err),
///     }
///
/// # });
/// ```
#[allow(deprecated)]
#[cfg(target_os = "linux")]
#[deprecated(
    since = "4.3.0",
    note = "Currently unstable. Use `simple_decrypt` if possible."
)]
pub async fn decrypt_text(data: Stringy) -> Result<Stringy, ErrorArrayItem> {
    let data_bytes: &[u8] = data.as_bytes();
    let decrypted_bytes: Vec<u8> = decrypt_data(&data_bytes).await.uf_unwrap()?;
    let decrypted_string: String = String::from_utf8(decrypted_bytes)?;
    let decrypted_stringy: Stringy = Stringy::Immutable(Arc::<str>::from(decrypted_string));

    Ok(decrypted_stringy)
}

/// Encrypts raw byte data using the legacy RECS-based encryption system, producing
/// a `UnifiedResult<Vec<u8>>` containing the cipher text (with key & other metadata).
///
/// # Deprecation
/// Marked as **deprecated** since version 4.3.0.
/// Please use [`simple_encrypt`] instead if possible.
///
/// # Arguments
/// - `data`: The byte slice to encrypt.
///
/// # Returns
/// - `UnifiedResult<Vec<u8>>`: On success, returns a byte vector containing the encrypted data.
///   On failure, returns an `ErrorArrayItem` describing what went wrong.
///
/// # Behavior
/// This function attempts multiple times (up to `attempts`) to acquire a lock if the
/// system is busy. If it remains locked, it returns an error.
#[deprecated(
    since = "4.3.0",
    note = "Currently unstable. Use `simple_encrypt` if possible."
)]
#[cfg(target_os = "linux")]
pub async fn encrypt_data(_data: &[u8]) -> UnifiedResult<Vec<u8>> {
    UnifiedResult::new(Ok(Vec::new()))
    // if let Err(err) = initialize_locker().await {
    //     return UnifiedResult::new(Err(err));
    // };

    // let attempts: u8 = 10;
    // let mut tries: u8 = 0;

    // while tries <= attempts {
    //     if execution_locked().await {
    //         tries += 1;
    //         tokio::time::sleep(Duration::from_millis(700)).await;
    //         continue;
    //     }

    //     match encrypt_raw(unsafe { String::from_utf8_unchecked(data.to_vec()) })
    //         .await
    //         .uf_unwrap()
    //     {
    //         Ok((key, data, count)) => {
    //             call_clean().await;

    //             return UnifiedResult::new(Ok(format!("{}-{}-{}", data, key, count)
    //                 .as_bytes()
    //                 .to_vec()));
    //         }
    //         Err(e) => {
    //             log!(LogLevel::Error, "{}", e);
    //             call_clean().await;
    //             unimplemented!()
    //         }
    //     }
    // }

    // return UnifiedResult::new(Err(ErrorArrayItem::new(
    //     Errors::GeneralError,
    //     "Attempted too many times to access RECS; system busy".to_owned(),
    // )));
}

/// Decrypts raw byte data using the legacy RECS-based decryption system. Expects
/// the data to contain key and count metadata (separated by '-').
///
/// # Deprecation
/// Marked as **deprecated** since version 4.3.0.
/// Please use [`simple_decrypt`] if possible.
///
/// # Arguments
/// - `data`: The byte slice to decrypt.  
///
/// # Returns
/// - `UnifiedResult<Vec<u8>>`: On success, returns a byte vector containing the decrypted data.
///   On failure, returns an `ErrorArrayItem` describing the error.
///
/// # Behavior
/// Repeatedly checks if the system is locked. If locked, it waits and retries.
/// Data must be in the format `[encrypted_data]-[key]-[count]`.
#[deprecated(
    since = "4.3.0",
    note = "Currently unstable. Use `simple_decrypt` if possible."
)]
#[cfg(target_os = "linux")]
pub async fn decrypt_data(_data: &[u8]) -> UnifiedResult<Vec<u8>> {
    UnifiedResult::new(Ok(Vec::new()))
    // if let Err(err) = initialize_locker().await {
    //     return UnifiedResult::new(Err(err));
    // };

    // let attempts: u8 = 10;
    // let mut tries: u8 = 0;

    // while tries <= attempts {
    //     if execution_locked().await {
    //         tries += 1;
    //         tokio::time::sleep(Duration::from_millis(700)).await;
    //         continue;
    //     }

    //     let data_str = match std::str::from_utf8(data) {
    //         Ok(s) => s,
    //         Err(e) => {
    //             log!(LogLevel::Error, "Invalid UTF-8 sequence: {}", e);
    //             return UnifiedResult::new(Err(ErrorArrayItem::from(e)));
    //         }
    //     };

    //     let parts: Vec<&str> = data_str.split('-').collect();

    //     if parts.len() != 3 {
    //         log!(LogLevel::Error, "Invalid input data format");
    //         return UnifiedResult::new(Err(ErrorArrayItem::new(
    //             Errors::InvalidType,
    //             "Input data does not contain key, data, and count separated by '-'".to_string(),
    //         )));
    //     }

    //     let cleaned_parts: Vec<String> = parts.iter().map(|part| part.replace("-", "")).collect();

    //     let key = cleaned_parts[1].to_string();
    //     let encrypted_data = cleaned_parts[0].to_string();
    //     let count = match cleaned_parts[2].parse::<usize>() {
    //         Ok(c) => c,
    //         Err(e) => {
    //             log!(LogLevel::Error, "Invalid count value: {}", e);
    //             1
    //         }
    //     };

    //     match decrypt_raw(encrypted_data, key, count).uf_unwrap() {
    //         Ok(data) => return UnifiedResult::new(Ok(data)),
    //         Err(e) => return UnifiedResult::new(Err(e)),
    //     }
    // }

    // return UnifiedResult::new(Err(ErrorArrayItem::new(
    //     Errors::GeneralError,
    //     "Attempted too many times to access RECS; system busy".to_owned(),
    // )));
}

/// Indicates whether the encryption/decryption process is currently locked
/// due to a housekeeping operation. Logs a warning if a lock is active.
///
/// # Returns
/// `true` if locked (housekeeping is in progress), otherwise `false`.
#[cfg(target_os = "linux")]
async fn _execution_locked() -> bool {
    // let lock = cleaning_lock.load(Ordering::Acquire);
    // if lock {
    //     log!(LogLevel::Warn, "RECS locked for cleaning");
    // }
    // lock
    false
}

/// Temporarily prevents the RECS cleaning operation from happening while
/// the provided `callback` is executed, to avoid clearing temporary data too soon.
///
/// # Safety
/// This function is marked as `unsafe` because it uses `unsafe` string
/// conversions internally. Only use it if you are certain the input data
/// can be safely converted to `String`. Also This function can lead to
/// unessacery filling of the /opt/artisan/tmp dir if used too many times as the cleaning
/// loop looses its refrence to the tmp recs data called this way
///
/// # Deprecation
/// Marked as **deprecated** since version 4.3.0.  
/// Prefer using `simple_*` functions that do not rely on legacy RECS mechanics.
///
/// # Arguments
/// - `callback`: A function or closure that performs an encryption/decryption operation.
/// - `data`: The byte slice on which the operation acts.
///
/// # Returns
/// - `Ok(Vec<u8>)`: The operation’s successful output.
/// - `Err(ErrorArrayItem)`: An error if the operation or housekeeping fails.
#[cfg(target_os = "linux")]
#[deprecated(
    since = "4.3.0",
    note = "Currently unstable. Use `simple_*` if possible."
)]
pub async unsafe fn clean_override_op<'a, F, Fut>(
    callback: F,
    data: &'a [u8],
) -> Result<Vec<u8>, ErrorArrayItem>
where
    F: Fn(&'a [u8]) -> Fut,
    Fut: std::future::Future<Output = UnifiedResult<Vec<u8>>>,
{
    cleaning_loop_initialized.store(true, Ordering::Relaxed);
    let result: Vec<u8> = callback(&data).await.uf_unwrap()?;
    // if let Err(err) = house_keeping().await {
    //     log!(LogLevel::Error, "HouseKeeping: {}", err);
    // }
    Ok(result)
}

/// Triggers RECS cleanup, notifying the `clean_loop` to proceed.
#[cfg(target_os = "linux")]
async fn _call_clean() {
    cleaning_call.notify_one();
    log!(LogLevel::Trace, "Recs clean called");
}

/// An asynchronous loop that waits for notifications to clean up RECS data.
/// Once triggered, it acquires a lock, performs housekeeping, and releases the lock.
#[cfg(target_os = "linux")]
async fn _clean_loop() -> Result<(), ErrorArrayItem> {
    cleaning_loop_initialized.store(true, Ordering::Release);
    loop {
        tokio::select! {
            _ = cleaning_call.notified() => {
                cleaning_lock.store(true, Ordering::SeqCst);
                tokio::time::sleep(Duration::from_millis(300)).await;
                // * Anything less than 250 may start cleaning before operations have finished
                // if let Err(err) = house_keeping().await {
                //     log!(LogLevel::Error, "HouseKeeping: {}", err);
                // }
                cleaning_lock.store(false, Ordering::SeqCst);
            }
        }
        sleep(Duration::from_secs(6)).await;
    }
}

/// Initializes the legacy RECS-based encryption system if it hasn't been
/// initialized yet. Also spawns the cleaning loop if not already running.
///
/// # Returns
/// - `Ok(())` on successful initialization.
/// - `Err(ErrorArrayItem)` if initialization fails.
#[cfg(target_os = "linux")]
async fn _initialize_locker() -> Result<(), ErrorArrayItem> {
    match initialized.load(Ordering::Relaxed) {
        true => {
            if !cleaning_loop_initialized.load(Ordering::Relaxed) {
                // tokio::spawn(clean_loop());
            }
            Ok(())
        }
        false => {
            // initialize(true).await.uf_unwrap()?;
            sleep(Duration::from_nanos(100)).await;
            initialized.store(true, Ordering::Relaxed);
            // tokio::spawn(clean_loop());
            cleaning_loop_initialized.store(true, Ordering::Relaxed);
            Ok(())
        }
    }
}

// endregion: Legacy Encryption/Decryption

// region: Modern Encryption/Decryption

/// The size (in bytes) of the GCM nonce. GCM requires a 96-bit (12-byte) nonce.
#[allow(unused_assignments)]
const NONCE_SIZE: usize = 12;

/// The size (in bytes) of the AES-256 key (256 bits → 32 bytes).
const KEY_SIZE: usize = 32;

// FIXME: (rename) This is comically critical to be in the middle of deped code, move this or rename it
pub fn generate_key(buffer: &mut [u8]) {
    let mut rng = rand::rng(); // Create a random number generator
    for byte in buffer.iter_mut() {
        *byte = rng.random(); // Fill each byte with random data
    }
}

/// Encrypts the provided data using AES-256 GCM encryption.
///
/// This modern approach is recommended over the legacy RECS-based system.
///
/// # Arguments
/// - `data`: Byte slice of the plaintext data to be encrypted.
///
/// # Returns
/// - `Ok(Stringy)`: A hex-encoded string containing the key, nonce, and ciphertext.
/// - `Err(ErrorArrayItem)`: An error if encryption fails.
pub fn simple_encrypt(data: &[u8]) -> Result<Stringy, ErrorArrayItem> {
    // Generate a random key and nonce
    let mut key: [u8; 32] = [0u8; 32];
    generate_key(&mut key);
    let cipher = Aes256Gcm::new(&key.into());
    let nonce_bytes = rand::rng().random::<[u8; NONCE_SIZE]>();
    let nonce = Nonce::<U12>::try_from(nonce_bytes.as_slice()).expect("nonce size mismatch");

    // Encrypt the data
    let ciphertext = cipher
        .encrypt(&nonce, data)
        .map_err(|e| ErrorArrayItem::new(Errors::InvalidBlockData, e.to_string()))?;

    // Combine the key, nonce, and ciphertext into a single byte stream
    let mut result = Vec::with_capacity(KEY_SIZE + NONCE_SIZE + ciphertext.len());
    result.extend_from_slice(&key);
    result.extend_from_slice(&nonce);
    result.extend_from_slice(&ciphertext);

    let cipher_text = Stringy::from(hex::encode(result));

    Ok(cipher_text)
}

/// Decrypts the provided data using AES-256 GCM decryption.
///
/// # Arguments
/// - `encrypted_cipher_data`: A hex-encoded string containing the key, nonce, and ciphertext.
///
/// # Returns
/// - `Ok(Vec<u8>)`: The decrypted plaintext data.
/// - `Err(ErrorArrayItem)`: An error if decryption fails or if data is malformed (too short).
pub fn simple_decrypt(encrypted_cipher_data: &[u8]) -> Result<Vec<u8>, ErrorArrayItem> {
    let encrypted_data: Vec<u8> =
        hex::decode(encrypted_cipher_data).map_err(ErrorArrayItem::from)?;

    // Extract the key, nonce, and ciphertext
    if encrypted_data.len() <= KEY_SIZE + NONCE_SIZE {
        return Err(ErrorArrayItem::new(
            Errors::InvalidBlockData,
            "Encrypted data is too short",
        ));
    }

    let key = <&Key<Aes256Gcm>>::try_from(&encrypted_data[..KEY_SIZE])
        .map_err(|_| ErrorArrayItem::new(Errors::InvalidBlockData, "Invalid key length"))?;
    let cipher = Aes256Gcm::new(key);
    let nonce = <&Nonce<U12>>::try_from(&encrypted_data[KEY_SIZE..KEY_SIZE + NONCE_SIZE])
        .map_err(|_| ErrorArrayItem::new(Errors::InvalidBlockData, "Invalid nonce length"))?;
    let ciphertext = &encrypted_data[KEY_SIZE + NONCE_SIZE..];

    // Decrypt the data
    cipher
        .decrypt(nonce, ciphertext)
        .map_err(|err| ErrorArrayItem::new(Errors::InvalidBlockData, err.to_string()))
}
// endregion: Modern Encryption/Decryption