From c3039c2b5e5426271fbe96df522979c0ce0347ed Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Tue, 5 Nov 2024 21:38:39 +0100 Subject: [PATCH] Added some documentation --- runtime/src/api_token.rs | 9 +++++++++ runtime/src/app_window.rs | 9 +++++++-- runtime/src/certificate.rs | 6 ++++++ runtime/src/clipboard.rs | 2 ++ runtime/src/dotnet.rs | 5 +++++ runtime/src/encryption.rs | 15 +++++++++++++-- runtime/src/environment.rs | 6 ++++++ runtime/src/log.rs | 4 +++- runtime/src/main.rs | 1 - runtime/src/network.rs | 1 + runtime/src/runtime_api.rs | 10 ++++++---- runtime/src/secret.rs | 8 ++++++++ 12 files changed, 66 insertions(+), 10 deletions(-) diff --git a/runtime/src/api_token.rs b/runtime/src/api_token.rs index 8db129e0..3fe1f182 100644 --- a/runtime/src/api_token.rs +++ b/runtime/src/api_token.rs @@ -5,6 +5,7 @@ use rocket::http::Status; use rocket::Request; use rocket::request::FromRequest; +/// The API token used to authenticate requests. pub static API_TOKEN: Lazy = Lazy::new(|| { let mut token = [0u8; 32]; let mut rng = rand_chacha::ChaChaRng::from_entropy(); @@ -16,11 +17,13 @@ pub static API_TOKEN: Lazy = Lazy::new(|| { token }); +/// The API token data structure used to authenticate requests. pub struct APIToken { hex_text: String, } impl APIToken { + /// Creates a new API token from a byte vector. fn from_bytes(bytes: Vec) -> Self { APIToken { hex_text: bytes.iter().fold(String::new(), |mut result, byte| { @@ -30,6 +33,7 @@ impl APIToken { } } + /// Creates a new API token from a hexadecimal text. fn from_hex_text(hex_text: &str) -> Self { APIToken { hex_text: hex_text.to_string(), @@ -40,17 +44,21 @@ impl APIToken { self.hex_text.as_str() } + /// Validates the received token against the valid token. fn validate(&self, received_token: &Self) -> bool { received_token.to_hex_text() == self.to_hex_text() } } +/// The request outcome type used to handle API token requests. type RequestOutcome = rocket::request::Outcome; +/// The request outcome implementation for the API token. #[rocket::async_trait] impl<'r> FromRequest<'r> for APIToken { type Error = APITokenError; + /// Handles the API token requests. async fn from_request(request: &'r Request<'_>) -> RequestOutcome { let token = request.headers().get_one("token"); match token { @@ -68,6 +76,7 @@ impl<'r> FromRequest<'r> for APIToken { } } +/// The API token error types. #[derive(Debug)] pub enum APITokenError { Missing, diff --git a/runtime/src/app_window.rs b/runtime/src/app_window.rs index 0ec5eb31..e5a5bc92 100644 --- a/runtime/src/app_window.rs +++ b/runtime/src/app_window.rs @@ -13,12 +13,13 @@ use crate::dotnet::stop_dotnet_server; use crate::environment::{is_prod, CONFIG_DIRECTORY, DATA_DIRECTORY}; use crate::log::switch_to_file_logging; -// The Tauri main window. +/// The Tauri main window. static MAIN_WINDOW: Lazy>> = Lazy::new(|| Mutex::new(None)); -// The update response coming from the Tauri updater. +/// The update response coming from the Tauri updater. static CHECK_UPDATE_RESPONSE: Lazy>>> = Lazy::new(|| Mutex::new(None)); +/// Starts the Tauri app. pub fn start_tauri() { info!("Starting Tauri app..."); let app = tauri::Builder::default() @@ -118,6 +119,7 @@ pub fn start_tauri() { } } +/// Changes the location of the main window to the given URL. pub async fn change_location_to(url: &str) { // Try to get the main window. If it is not available yet, wait for it: let mut main_window_ready = false; @@ -149,6 +151,7 @@ pub async fn change_location_to(url: &str) { } } +/// Checks for updates. #[get("/updates/check")] pub async fn check_for_update(_token: APIToken) -> Json { let app_handle = MAIN_WINDOW.lock().unwrap().as_ref().unwrap().app_handle(); @@ -194,6 +197,7 @@ pub async fn check_for_update(_token: APIToken) -> Json { } } +/// The response to the check for update request. #[derive(Serialize)] pub struct CheckUpdateResponse { update_is_available: bool, @@ -202,6 +206,7 @@ pub struct CheckUpdateResponse { changelog: String, } +/// Installs the update. #[get("/updates/install")] pub async fn install_update(_token: APIToken) { let cloned_response_option = CHECK_UPDATE_RESPONSE.lock().unwrap().clone(); diff --git a/runtime/src/certificate.rs b/runtime/src/certificate.rs index 2f212195..2f6bc715 100644 --- a/runtime/src/certificate.rs +++ b/runtime/src/certificate.rs @@ -3,10 +3,16 @@ use log::info; use rcgen::generate_simple_self_signed; use sha2::{Sha256, Digest}; +/// The certificate used for the runtime API server. pub static CERTIFICATE: OnceLock> = OnceLock::new(); + +/// The private key used for the certificate of the runtime API server. pub static CERTIFICATE_PRIVATE_KEY: OnceLock> = OnceLock::new(); + +/// The fingerprint of the certificate used for the runtime API server. pub static CERTIFICATE_FINGERPRINT: OnceLock = OnceLock::new(); +/// Generates a TLS certificate for the runtime API server. pub fn generate_certificate() { info!("Try to generate a TLS certificate for the runtime API server..."); diff --git a/runtime/src/clipboard.rs b/runtime/src/clipboard.rs index feea75a4..b00617f2 100644 --- a/runtime/src/clipboard.rs +++ b/runtime/src/clipboard.rs @@ -6,6 +6,7 @@ use serde::Serialize; use crate::api_token::APIToken; use crate::encryption::{EncryptedText, ENCRYPTION}; +/// Sets the clipboard text to the provided encrypted text. #[post("/clipboard/set", data = "")] pub fn set_clipboard(_token: APIToken, encrypted_text: EncryptedText) -> Json { @@ -53,6 +54,7 @@ pub fn set_clipboard(_token: APIToken, encrypted_text: EncryptedText) -> Json = Lazy::new(|| get_available_port().unwrap( static DOTNET_INITIALIZED: Lazy> = Lazy::new(|| Mutex::new(false)); +/// Returns the desired port of the .NET server. Our .NET app calls this endpoint to get +/// the port where the .NET server should listen to. #[get("/system/dotnet/port")] pub fn dotnet_port(_token: APIToken) -> String { let dotnet_server_port = *DOTNET_SERVER_PORT; format!("{dotnet_server_port}") } +/// Starts the .NET server in a separate process. pub fn start_dotnet_server() { // Get the secret password & salt and convert it to a base64 string: @@ -128,6 +131,7 @@ pub fn start_dotnet_server() { }); } +/// This endpoint is called by the .NET server to signal that the server is ready. #[get("/system/dotnet/ready")] pub async fn dotnet_ready(_token: APIToken) { @@ -158,6 +162,7 @@ pub async fn dotnet_ready(_token: APIToken) { change_location_to(url.as_str()).await; } +/// Stops the .NET server process. pub fn stop_dotnet_server() { if let Some(server_process) = DOTNET_SERVER.lock().unwrap().take() { let server_kill_result = server_process.kill(); diff --git a/runtime/src/encryption.rs b/runtime/src/encryption.rs index bd7a4687..d28dbf27 100644 --- a/runtime/src/encryption.rs +++ b/runtime/src/encryption.rs @@ -21,6 +21,7 @@ type Aes256CbcDec = cbc::Decryptor; type DataOutcome<'r, T> = data::Outcome<'r, T>; +/// The encryption instance used for the IPC channel. pub static ENCRYPTION: Lazy = Lazy::new(|| { // // Generate a secret key & salt for the AES encryption for the IPC channel: @@ -41,6 +42,7 @@ pub static ENCRYPTION: Lazy = Lazy::new(|| { Encryption::new(&secret_key, &secret_key_salt).unwrap() }); +/// The encryption struct used for the IPC channel. pub struct Encryption { key: [u8; 32], iv: [u8; 16], @@ -58,6 +60,7 @@ impl Encryption { // algorithms we chose, requires a fixed key length, and our password is too long. const ITERATIONS: u32 = 100; + /// Initializes the encryption with the given secret password and salt. pub fn new(secret_password: &[u8], secret_key_salt: &[u8]) -> Result { if secret_password.len() != 512 { return Err("The secret password must be 512 bytes long.".to_string()); @@ -92,6 +95,7 @@ impl Encryption { Ok(encryption) } + /// Encrypts the given data. pub fn encrypt(&self, data: &str) -> Result { let cipher = Aes256CbcEnc::new(&self.key.into(), &self.iv.into()); let encrypted = cipher.encrypt_padded_vec_mut::(data.as_bytes()); @@ -100,6 +104,7 @@ impl Encryption { Ok(EncryptedText::new(result)) } + /// Decrypts the given data. pub fn decrypt(&self, encrypted_data: &EncryptedText) -> Result { let decoded = BASE64_STANDARD.decode(encrypted_data.get_encrypted()).map_err(|e| format!("Error decoding base64: {e}"))?; @@ -119,14 +124,18 @@ impl Encryption { } } +/// Represents encrypted text. #[derive(Clone, Serialize, Deserialize)] pub struct EncryptedText(String); impl EncryptedText { + + /// Creates a new encrypted text instance. pub fn new(encrypted_data: String) -> Self { EncryptedText(encrypted_data) } + /// Returns the encrypted data. pub fn get_encrypted(&self) -> &str { &self.0 } @@ -144,11 +153,13 @@ impl fmt::Display for EncryptedText { } } -// Use Case: When we receive encrypted text from the client as body (e.g., in a POST request). -// We must interpret the body as EncryptedText. +/// Use Case: When we receive encrypted text from the client as body (e.g., in a POST request). +/// We must interpret the body as EncryptedText. #[rocket::async_trait] impl<'r> data::FromData<'r> for EncryptedText { type Error = String; + + /// Parses the data as EncryptedText. async fn from_data(req: &'r Request<'_>, data: Data<'r>) -> DataOutcome<'r, Self> { let content_type = req.content_type(); if content_type.map_or(true, |ct| !ct.is_text()) { diff --git a/runtime/src/environment.rs b/runtime/src/environment.rs index f0f33fe7..53e8f8fe 100644 --- a/runtime/src/environment.rs +++ b/runtime/src/environment.rs @@ -2,10 +2,13 @@ use std::sync::OnceLock; use rocket::get; use crate::api_token::APIToken; +/// The data directory where the application stores its data. pub static DATA_DIRECTORY: OnceLock = OnceLock::new(); +/// The config directory where the application stores its configuration. pub static CONFIG_DIRECTORY: OnceLock = OnceLock::new(); +/// Returns the config directory. #[get("/system/directories/config")] pub fn get_config_directory(_token: APIToken) -> String { match CONFIG_DIRECTORY.get() { @@ -14,6 +17,7 @@ pub fn get_config_directory(_token: APIToken) -> String { } } +/// Returns the data directory. #[get("/system/directories/data")] pub fn get_data_directory(_token: APIToken) -> String { match DATA_DIRECTORY.get() { @@ -22,10 +26,12 @@ pub fn get_data_directory(_token: APIToken) -> String { } } +/// Returns true if the application is running in development mode. pub fn is_dev() -> bool { cfg!(debug_assertions) } +/// Returns true if the application is running in production mode. pub fn is_prod() -> bool { !is_dev() } \ No newline at end of file diff --git a/runtime/src/log.rs b/runtime/src/log.rs index 476b3d9d..30befd46 100644 --- a/runtime/src/log.rs +++ b/runtime/src/log.rs @@ -11,6 +11,7 @@ use crate::environment::is_dev; static LOGGER: OnceLock = OnceLock::new(); +/// Initialize the logging system. pub fn init_logging() { // @@ -60,6 +61,7 @@ pub fn init_logging() { LOGGER.set(runtime_logger).expect("Cannot set LOGGER"); } +/// Switch the logging system to a file-based output. pub fn switch_to_file_logging(logger_path: PathBuf) -> Result<(), Box>{ LOGGER.get().expect("No LOGGER was set").handle.reset_flw(&FileLogWriter::builder( FileSpec::default() @@ -137,7 +139,7 @@ fn terminal_colored_logger_format( write!(w, "{}", flexi_logger::style(level).paint(record.args().to_string())) } -// Custom LOGGER format for the log files: +/// Custom LOGGER format for the log files: fn file_logger_format( w: &mut dyn std::io::Write, now: &mut DeferredNow, diff --git a/runtime/src/main.rs b/runtime/src/main.rs index 2d8c7a39..3b0026d4 100644 --- a/runtime/src/main.rs +++ b/runtime/src/main.rs @@ -28,7 +28,6 @@ async fn main() { let app_commit_hash = metadata_lines.next().unwrap(); init_logging(); - info!("Starting MindWork AI Studio:"); let working_directory = std::env::current_dir().unwrap(); diff --git a/runtime/src/network.rs b/runtime/src/network.rs index e20e1a06..0e148a1d 100644 --- a/runtime/src/network.rs +++ b/runtime/src/network.rs @@ -1,5 +1,6 @@ use std::net::TcpListener; +/// Returns an available port on the local machine. pub fn get_available_port() -> Option { TcpListener::bind(("127.0.0.1", 0)) .map(|listener| listener.local_addr().unwrap().port()) diff --git a/runtime/src/runtime_api.rs b/runtime/src/runtime_api.rs index 44a0339d..4c12871d 100644 --- a/runtime/src/runtime_api.rs +++ b/runtime/src/runtime_api.rs @@ -8,10 +8,10 @@ use crate::certificate::{CERTIFICATE, CERTIFICATE_PRIVATE_KEY}; use crate::environment::is_dev; use crate::network::get_available_port; -// The port used for the runtime API server. In the development environment, we use a fixed -// port, in the production environment we use the next available port. This differentiation -// is necessary because we cannot communicate the port to the .NET server in the development -// environment. +/// The port used for the runtime API server. In the development environment, we use a fixed +/// port, in the production environment we use the next available port. This differentiation +/// is necessary because we cannot communicate the port to the .NET server in the development +/// environment. pub static API_SERVER_PORT: Lazy = Lazy::new(|| { if is_dev() { 5000 @@ -20,6 +20,8 @@ pub static API_SERVER_PORT: Lazy = Lazy::new(|| { } }); +/// Starts the runtime API server. The server is used to communicate with the .NET server and +/// to provide additional functionality to the Tauri app. pub fn start_runtime_api() { let api_port = *API_SERVER_PORT; info!("Try to start the API server on 'http://localhost:{api_port}'..."); diff --git a/runtime/src/secret.rs b/runtime/src/secret.rs index 37948c94..5ae07c8b 100644 --- a/runtime/src/secret.rs +++ b/runtime/src/secret.rs @@ -7,6 +7,7 @@ use keyring::error::Error::NoEntry; use crate::api_token::APIToken; use crate::encryption::{EncryptedText, ENCRYPTION}; +/// Stores a secret in the secret store using the operating system's keyring. #[post("/secrets/store", data = "")] pub fn store_secret(_token: APIToken, request: Json) -> Json { let user_name = request.user_name.as_str(); @@ -43,6 +44,7 @@ pub fn store_secret(_token: APIToken, request: Json) -> Json) -> Json { let user_name = request.user_name.as_str(); @@ -100,6 +104,7 @@ pub fn get_secret(_token: APIToken, request: Json) -> Json) -> Json { let user_name = request.user_name.as_str(); @@ -151,6 +158,7 @@ pub fn delete_secret(_token: APIToken, request: Json) -> Json