2024-06-19 01:36:07 -04:00
|
|
|
use std::fmt;
|
|
|
|
|
|
|
|
|
|
// type of a guac message
|
|
|
|
|
pub type Elements = Vec<String>;
|
|
|
|
|
|
|
|
|
|
// FIXME: thiserror, please.
|
|
|
|
|
|
|
|
|
|
/// Errors during decoding
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub enum DecodeError {
|
2024-08-04 15:59:39 -04:00
|
|
|
/// Invalid guacamole instruction format
|
|
|
|
|
InvalidFormat,
|
2024-06-19 01:36:07 -04:00
|
|
|
|
2024-08-04 15:59:39 -04:00
|
|
|
/// Instruction is too long for the current decode policy.
|
|
|
|
|
InstructionTooLong,
|
2024-06-19 01:36:07 -04:00
|
|
|
|
2024-08-04 15:59:39 -04:00
|
|
|
/// Element is too long for the current decode policy.
|
|
|
|
|
ElementTooLong,
|
2024-06-19 01:36:07 -04:00
|
|
|
|
2024-08-04 15:59:39 -04:00
|
|
|
/// Invalid element size.
|
|
|
|
|
ElementSizeInvalid,
|
2024-06-19 01:36:07 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub type DecodeResult<T> = std::result::Result<T, DecodeError>;
|
|
|
|
|
|
|
|
|
|
impl fmt::Display for DecodeError {
|
2024-08-04 15:59:39 -04:00
|
|
|
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"),
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-06-19 01:36:07 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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<const INST_SIZE: usize, const ELEM_SIZE: usize>();
|
|
|
|
|
|
|
|
|
|
impl<const INST_SIZE: usize, const ELEM_SIZE: usize> StaticDecodePolicy<INST_SIZE, ELEM_SIZE> {
|
2024-08-04 15:59:39 -04:00
|
|
|
fn max_instruction_size(&self) -> usize {
|
|
|
|
|
INST_SIZE
|
|
|
|
|
}
|
2024-06-19 01:36:07 -04:00
|
|
|
|
2024-08-04 15:59:39 -04:00
|
|
|
fn max_element_size(&self) -> usize {
|
|
|
|
|
ELEM_SIZE
|
|
|
|
|
}
|
2024-06-19 01:36:07 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The default decode policy.
|
|
|
|
|
pub type DefaultDecodePolicy = StaticDecodePolicy<12288, 4096>;
|
|
|
|
|
|
|
|
|
|
/// Encodes elements into a Guacamole instruction
|
|
|
|
|
pub fn encode_instruction(elements: &Elements) -> String {
|
2024-08-04 15:59:39 -04:00
|
|
|
let mut str = String::new();
|
2024-06-19 01:36:07 -04:00
|
|
|
|
2024-08-04 15:59:39 -04:00
|
|
|
for elem in elements.iter() {
|
|
|
|
|
str.push_str(&format!("{}.{},", elem.len(), elem));
|
|
|
|
|
}
|
2024-06-19 01:36:07 -04:00
|
|
|
|
2024-08-04 15:59:39 -04:00
|
|
|
// hacky, but whatever
|
|
|
|
|
str.pop();
|
|
|
|
|
str.push(';');
|
2024-06-19 01:36:07 -04:00
|
|
|
|
2024-08-04 15:59:39 -04:00
|
|
|
str
|
2024-06-19 01:36:07 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Decodes a Guacamole instruction to individual elements
|
|
|
|
|
pub fn decode_instruction(element_string: &String) -> DecodeResult<Elements> {
|
2024-08-04 15:59:39 -04:00
|
|
|
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::<Vec<_>>();
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
// you can also just scan the value in by hand.)
|
|
|
|
|
//
|
|
|
|
|
// We bound this anyways and do quite the checks, so even though it's not great,
|
|
|
|
|
// it should be generally 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 '.' seperating the size and the element data;
|
|
|
|
|
// our integer scanning ensures we only get here in the case that this is actually the '.'
|
|
|
|
|
// character.
|
|
|
|
|
current_position += 1;
|
|
|
|
|
|
|
|
|
|
// Make sure the element size doesn't overflow the decode policy
|
|
|
|
|
// or the size of the whole instruction.
|
|
|
|
|
|
|
|
|
|
if element_size >= policy.max_element_size() {
|
|
|
|
|
return Err(DecodeError::ElementTooLong);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if element_size >= element_string.len() {
|
|
|
|
|
return Err(DecodeError::ElementSizeInvalid);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// cutoff elements or something
|
|
|
|
|
if current_position + element_size > chars.len() - 1 {
|
|
|
|
|
//println!("? {current_position} a {}", chars.len());
|
|
|
|
|
return Err(DecodeError::InvalidFormat);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let element = chars
|
|
|
|
|
.iter()
|
|
|
|
|
.skip(current_position)
|
|
|
|
|
.take(element_size)
|
|
|
|
|
.collect::<String>();
|
|
|
|
|
|
|
|
|
|
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)
|
2024-06-19 01:36:07 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
2024-08-04 15:59:39 -04:00
|
|
|
use super::*;
|
2024-06-19 01:36:07 -04:00
|
|
|
|
2024-08-04 15:59:39 -04:00
|
|
|
#[test]
|
|
|
|
|
fn decode_basic() {
|
|
|
|
|
let test = String::from("7.connect,3.vm1;");
|
|
|
|
|
let res = decode_instruction(&test);
|
2024-06-19 01:36:07 -04:00
|
|
|
|
2024-08-04 15:59:39 -04:00
|
|
|
assert!(res.is_ok());
|
|
|
|
|
assert_eq!(res.unwrap(), vec!["connect", "vm1"]);
|
|
|
|
|
}
|
2024-06-19 01:36:07 -04:00
|
|
|
|
2024-08-04 15:59:39 -04:00
|
|
|
#[test]
|
|
|
|
|
fn decode_errors() {
|
|
|
|
|
let test = String::from("700.connect,3.vm1;");
|
|
|
|
|
let res = decode_instruction(&test);
|
2024-06-19 01:36:07 -04:00
|
|
|
|
2024-08-04 15:59:39 -04:00
|
|
|
eprintln!("Error for: {}", res.clone().unwrap_err());
|
2024-06-19 01:36:07 -04:00
|
|
|
|
2024-08-04 15:59:39 -04:00
|
|
|
assert!(res.is_err())
|
|
|
|
|
}
|
2024-06-19 01:36:07 -04:00
|
|
|
|
2024-08-04 15:59:39 -04:00
|
|
|
// 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);
|
2024-06-19 01:36:07 -04:00
|
|
|
|
2024-08-04 15:59:39 -04:00
|
|
|
assert_eq!(test, "7.connect,3.vm1;");
|
2024-06-19 01:36:07 -04:00
|
|
|
|
2024-08-04 15:59:39 -04:00
|
|
|
let res = decode_instruction(&test);
|
2024-06-19 01:36:07 -04:00
|
|
|
|
2024-08-04 15:59:39 -04:00
|
|
|
assert!(res.is_ok());
|
|
|
|
|
assert_eq!(res.unwrap(), vec);
|
|
|
|
|
}
|
2024-06-19 01:36:07 -04:00
|
|
|
}
|