use crate::constants::{MAX_DECOMPRESSED_SIZE, STATE_MAX, VERSION};
use crate::{Error, state};
use base64::prelude::*;
use bincode::Options;
use flate2::Compression;
use flate2::read::ZlibDecoder;
use flate2::write::ZlibEncoder;
use serde::{Deserialize, Serialize};
use sha256::digest;
use std::fmt;
use std::io::prelude::*;
use std::str::FromStr;
use self::state::State;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Machine {
pub allowed_padding_packets: u64,
pub max_padding_frac: f64,
pub allowed_blocked_microsec: u64,
pub max_blocking_frac: f64,
pub states: Vec<State>,
}
impl Machine {
pub fn new(
allowed_padding_packets: u64,
max_padding_frac: f64,
allowed_blocked_microsec: u64,
max_blocking_frac: f64,
states: Vec<State>,
) -> Result<Self, Error> {
let machine = Machine {
allowed_padding_packets,
max_padding_frac,
allowed_blocked_microsec,
max_blocking_frac,
states,
};
machine.validate()?;
Ok(machine)
}
pub fn name(&self) -> String {
let s = digest(self.serialize());
s[0..32].to_string()
}
pub fn serialize(&self) -> String {
let bincoder = bincode::DefaultOptions::new().with_limit(MAX_DECOMPRESSED_SIZE as u64);
let encoded = bincoder.serialize(&self).unwrap();
let mut e = ZlibEncoder::new(Vec::new(), Compression::best());
e.write_all(encoded.as_slice()).unwrap();
let s = BASE64_STANDARD.encode(e.finish().unwrap());
format!("{VERSION:02}{s}")
}
pub fn validate(&self) -> Result<(), Error> {
if self.max_padding_frac < 0.0 || self.max_padding_frac > 1.0 {
return Err(Error::Machine(format!(
"max_padding_frac has to be [0.0, 1.0], got {}",
self.max_padding_frac
)));
}
if self.max_blocking_frac < 0.0 || self.max_blocking_frac > 1.0 {
return Err(Error::Machine(format!(
"max_blocking_frac has to be [0.0, 1.0], got {}",
self.max_blocking_frac
)));
}
let num_states = self.states.len();
if num_states == 0 {
Err(Error::Machine(
"a machine must have at least one state".to_string(),
))?;
}
if num_states > STATE_MAX {
Err(Error::Machine(format!(
"too many states, max is {STATE_MAX}, found {num_states}"
)))?;
}
for state in self.states.iter() {
state
.validate(num_states)
.map_err(|e| Error::Machine(e.to_string()))?;
}
Ok(())
}
}
impl FromStr for Machine {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() < 3 {
Err(Error::Machine("string too short".to_string()))?;
}
if !s.is_ascii() {
Err(Error::Machine("string is not ascii".to_string()))?;
}
let version = &s[0..2];
if version != format!("{VERSION:02}") {
Err(Error::Machine(format!(
"version mismatch, expected {VERSION}, got {version}"
)))?;
}
let s = &s[2..];
let compressed = BASE64_STANDARD.decode(s.as_bytes());
if compressed.is_err() {
Err(Error::Machine("base64 decoding failed".to_string()))?;
}
let compressed = compressed.unwrap();
let mut decoder = ZlibDecoder::new(compressed.as_slice());
let mut buf = vec![0; MAX_DECOMPRESSED_SIZE];
let bytes_read = decoder
.read(&mut buf)
.map_err(|e| Error::Machine(e.to_string()))?;
let bincoder = bincode::DefaultOptions::new().with_limit(MAX_DECOMPRESSED_SIZE as u64);
let r = bincoder.deserialize(&buf[..bytes_read]);
let m: Machine = r.map_err(|e| Error::Machine(e.to_string()))?;
m.validate()?;
Ok(m)
}
}
impl fmt::Display for Machine {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Machine {}\n\
- allowed_padding_packets: {}\n\
- max_padding_frac: {}\n\
- allowed_blocked_microsec: {}\n\
- max_blocking_frac: {}\n\
States:\n\
{}",
self.name(),
self.allowed_padding_packets,
self.max_padding_frac,
self.allowed_blocked_microsec,
self.max_blocking_frac,
self.states
.iter()
.map(|s| format!("{s}"))
.collect::<Vec<String>>()
.join("\n")
)
}
}
#[cfg(test)]
mod tests {
use crate::event::Event;
use crate::machine::*;
use crate::state::Trans;
use enum_map::enum_map;
#[test]
fn machine_name_generation() {
let s0 = State::new(enum_map! {
Event::PaddingSent => vec![Trans(0, 1.0)],
_ => vec![],
});
let m = Machine::new(1000, 1.0, 0, 0.0, vec![s0]).unwrap();
assert_eq!(m.name(), m.name());
}
#[test]
fn validate_machine_limits() {
let s0 = State::new(enum_map! {
Event::PaddingSent => vec![Trans(0, 1.0)],
_ => vec![],
});
let mut m = Machine::new(1000, 1.0, 0, 0.0, vec![s0]).unwrap();
m.max_padding_frac = -0.1;
let r = m.validate();
println!("{:?}", r.as_ref().err());
assert!(r.is_err());
m.max_padding_frac = 1.1;
let r = m.validate();
println!("{:?}", r.as_ref().err());
assert!(r.is_err());
m.max_padding_frac = 0.5;
let r = m.validate();
assert!(r.is_ok());
m.max_blocking_frac = -0.1;
let r = m.validate();
println!("{:?}", r.as_ref().err());
assert!(r.is_err());
m.max_blocking_frac = 1.1;
let r = m.validate();
println!("{:?}", r.as_ref().err());
assert!(r.is_err());
m.max_blocking_frac = 0.5;
let r = m.validate();
assert!(r.is_ok());
}
#[test]
fn validate_machine_num_states() {
let r = Machine::new(1000, 1.0, 0, 0.0, vec![]);
println!("{:?}", r.as_ref().err());
assert!(r.is_err());
}
#[test]
fn validate_machine_states() {
let s0 = State::new(enum_map! {
Event::PaddingSent => vec![Trans(1, 1.0)],
_ => vec![],
});
let r = Machine::new(1000, 1.0, 0, 0.0, vec![s0]);
println!("{:?}", r.as_ref().err());
assert!(r.is_err());
let s0 = State::new(enum_map! {
Event::PaddingSent => vec![Trans(0, 0.8)],
_ => vec![],
});
let r = Machine::new(1000, 1.0, 0, 0.0, vec![s0]);
assert!(r.is_ok());
}
}