From 92925ab4d249e5c6d9f2aae309d492cb37fd1f2a Mon Sep 17 00:00:00 2001 From: modeco80 Date: Thu, 28 Mar 2024 11:02:14 -0400 Subject: [PATCH] chore: init, very barren - guacutils --- .gitignore | 1 + Cargo.lock | 7 ++ Cargo.toml | 6 ++ README.md | 46 +++++++++++++ rustfmt.toml | 3 + src/guac.rs | 182 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + src/main.rs | 5 ++ 8 files changed, 251 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 rustfmt.toml create mode 100644 src/guac.rs create mode 100644 src/lib.rs create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..72f4502 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "cvm12-rs" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..1669d06 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "cvm12-rs" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/README.md b/README.md new file mode 100644 index 0000000..8be5894 --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +# cvm1.2-rs + +Stopgap part 4. Mostly made as a experiment, and maybe for production use?. + +# Usage + +There is none, at the moment. Once there is, there will be actual documentation here. + +## Noodling for configuration + +collabvm.toml +```toml +[collabvm] +listen = "127.0.0.1:3000" # or /run/cvm.sock ? if warp supports uds i guess + +# optional +vm_directory = "vms" + +# nested table. you can define it non-nested if you want +mod_permissions = { xxx = true, xyz = false } + +mod_password_hash = "argon2 hash" +admin_password_hash = "argon2 hash" + +# applied to vms by default unless they override +default_motd = "hice powered vms" + +ban_command = "sexy $user $ip" +``` + +vms/vm1.toml +```toml +[vm] +name = "Window XP have honse (VM 1)" + +# cvm1.2-rs will automatically append parameters as needed +command_line = "./vm1.lvm start" + +motd = "YOU HAVE FUCKER GOOGLE FROM 999999999.. Banned!" +``` + +# Building + +`cargo b --release` + +Unit tests can be run with `cargo test`, as is for most rust programs... diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..9080839 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,3 @@ +# sorry guys this isnt something im going to have an argument about +# other than that this is fine +hard_tabs = true \ No newline at end of file diff --git a/src/guac.rs b/src/guac.rs new file mode 100644 index 0000000..ff6b109 --- /dev/null +++ b/src/guac.rs @@ -0,0 +1,182 @@ +//! This guacutils impl was certified not mostly copilot generated +//! also omits diagnostic information, we don't need diagnostics at all +use std::fmt; + +// type of a guac message +pub type Elements = Vec; + +/// Errors during decoding +#[derive(Debug, Clone)] +pub enum DecodeError { + /// Invalid guacamole instruction format + InvalidFormat, + + /// Instruction is too long for the current decode policy. + InstructionTooLong, + + /// Element is too long for the current decode policy. + ElementTooLong, + + /// Invalid element size. + ElementSizeInvalid, +} + +pub type DecodeResult = std::result::Result; + +impl fmt::Display for DecodeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::InvalidFormat => write!(f, "Invalid Guacamole instruction while decoding"), + Self::InstructionTooLong => write!(f, "Instruction too long for current decode policy"), + Self::ElementTooLong => write!(f, "Element too long for current decode policy"), + Self::ElementSizeInvalid => write!(f, "Element size is invalid") + } + } +} + +// this decode policy abstraction would in theory be useful, +// but idk how to do this kind of thing in rust very well + +pub struct StaticDecodePolicy(); + +impl StaticDecodePolicy { + fn max_instruction_size(&self) -> usize { + INST_SIZE + } + + fn max_element_size(&self) -> usize { + ELEM_SIZE + } +} + +/// The default decode policy. +pub type DefaultDecodePolicy = StaticDecodePolicy<12288, 4096>; + + +/// Encodes elements into a Guacamole instruction +pub fn encode_instruction(elements: &Elements) -> String { + let mut str = String::new(); + + for elem in elements.iter() { + str.push_str(&format!("{}.{},", elem.len(), elem)); + } + + // hacky, but whatever + str.pop(); + str.push(';'); + + str +} + +/// Decodes a Guacamole instruction to individual elements +pub fn decode_instruction(element_string: &String) -> DecodeResult { + let policy = DefaultDecodePolicy {}; + + let mut vec: Elements = Vec::new(); + let mut current_position: usize = 0; + + // Instruction is too long. Don't even bother + if policy.max_instruction_size() < element_string.len() { + return Err(DecodeError::InstructionTooLong); + } + + let chars = element_string.chars().collect::>(); + + loop { + let mut element_size: usize = 0; + + // Scan the integer value in by hand. This is mostly because + // I'm stupid, and the Rust integer parsing routines (seemingly) + // require a substring (or a slice, but if you can generate a slice bound, + // you can also just scan the value in by hand.....) + // + // We bound this anyways so it's fine(TM) + loop { + let c = chars[current_position]; + + if c >= '0' && c <= '9' { + element_size = element_size * 10 + (c as usize) - ('0' as usize); + } else { + if c == '.' { + break; + } + + return Err(DecodeError::InvalidFormat); + } + current_position += 1; + } + + // eat the '.'; our integer scanning ensures + // we only get here in that case + current_position += 1; + + if element_size >= policy.max_element_size() { + return Err(DecodeError::ElementTooLong); + } + + if element_size >= element_string.len() { + return Err(DecodeError::ElementSizeInvalid); + } + + let element = chars + .iter() + .skip(current_position) + .take(element_size) + .collect::(); + + current_position += element_size; + + vec.push(element); + + // make sure seperator is proper + match chars[current_position] { + ',' => {} + ';' => break, + _ => return Err(DecodeError::InvalidFormat), + } + + // eat the ',' + current_position += 1; + } + + Ok(vec) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn decode_basic() { + let test = String::from("7.connect,3.vm1;"); + let res = decode_instruction(&test); + + assert!(res.is_ok()); + assert_eq!(res.unwrap(), vec!["connect", "vm1"]); + } + + #[test] + fn decode_errors() { + let test = String::from("700.connect,3.vm1;"); + let res = decode_instruction(&test); + + eprintln!("Error for: {}", res.clone().unwrap_err()); + + assert!(res.is_err()) + } + + // generally just test that the codec even works + // (we can decode a instruction we created) + #[test] + fn general_codec_works() { + let vec = vec![String::from("connect"), String::from("vm1")]; + let test = encode_instruction(&vec); + + assert_eq!(test, "7.connect,3.vm1;"); + + let res = decode_instruction(&test); + + assert!(res.is_ok()); + assert_eq!(res.unwrap(), vec); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..c1d2b39 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1 @@ +pub mod guac; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..e226533 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,5 @@ +use cvm12_rs::guac; + +fn main() { + println!("Hello, world!"); +}