use crate::anytls::anytls_types::StringMap;
use md5::{Digest, Md5};
use rand::RngExt;
use std::sync::Arc;
pub const CHECK_MARK: i32 = -1;
pub const DEFAULT_PADDING_SCHEME: &str = r#"stop=8
0=30-30
1=100-400
2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000
3=9-9,500-1000
4=500-1000
5=500-1000
6=500-1000
7=500-1000"#;
#[derive(Debug, Clone)]
pub struct PaddingFactory {
scheme: StringMap,
raw_scheme: Vec<u8>,
stop: u32,
md5: String,
}
impl PaddingFactory {
pub fn new(raw_scheme: &[u8]) -> Result<Self, String> {
let scheme = StringMap::from_bytes(raw_scheme);
let stop = scheme
.get("stop")
.ok_or_else(|| "missing 'stop' in padding scheme".to_string())?
.parse::<u32>()
.map_err(|_| "invalid 'stop' value in padding scheme".to_string())?;
let mut hasher = Md5::new();
hasher.update(raw_scheme);
let md5_result: [u8; 16] = hasher.finalize().into();
let md5 = md5_result
.iter()
.map(|b| format!("{:02x}", b))
.collect::<String>();
Ok(Self {
scheme,
raw_scheme: raw_scheme.to_vec(),
stop,
md5,
})
}
pub fn default_factory() -> Arc<Self> {
Arc::new(
Self::new(DEFAULT_PADDING_SCHEME.as_bytes())
.expect("default padding scheme should be valid"),
)
}
pub fn stop(&self) -> u32 {
self.stop
}
pub fn md5(&self) -> &str {
&self.md5
}
pub fn raw_scheme(&self) -> &[u8] {
&self.raw_scheme
}
pub fn generate_record_payload_sizes(&self, pkt: u32) -> Vec<i32> {
let key = pkt.to_string();
let Some(spec) = self.scheme.get(&key) else {
return Vec::new();
};
let mut sizes = Vec::new();
let parts: Vec<&str> = spec.split(',').collect();
for part in parts {
let part = part.trim();
if part == "c" {
sizes.push(CHECK_MARK);
continue;
}
if let Some((min_str, max_str)) = part.split_once('-') {
let min_val: i64 = match min_str.trim().parse() {
Ok(v) if v > 0 => v,
_ => continue,
};
let max_val: i64 = match max_str.trim().parse() {
Ok(v) if v > 0 => v,
_ => continue,
};
let (min_val, max_val) = (min_val.min(max_val), min_val.max(max_val));
if min_val == max_val {
sizes.push(min_val as i32);
} else {
let mut rng = rand::rng();
let size = rng.random_range(min_val..=max_val);
sizes.push(size as i32);
}
}
}
sizes
}
}
impl Default for PaddingFactory {
fn default() -> Self {
Self::new(DEFAULT_PADDING_SCHEME.as_bytes())
.expect("default padding scheme should be valid")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_factory() {
let factory = PaddingFactory::default();
assert_eq!(factory.stop(), 8);
assert!(!factory.md5().is_empty());
}
#[test]
fn test_generate_fixed_size() {
let factory = PaddingFactory::default();
let sizes = factory.generate_record_payload_sizes(0);
assert_eq!(sizes, vec![30]);
}
#[test]
fn test_generate_random_range() {
let factory = PaddingFactory::default();
for _ in 0..100 {
let sizes = factory.generate_record_payload_sizes(1);
assert_eq!(sizes.len(), 1);
assert!(sizes[0] >= 100 && sizes[0] <= 400);
}
}
#[test]
fn test_check_mark() {
let scheme = "stop=3\n2=400-500,c,500-1000";
let factory = PaddingFactory::new(scheme.as_bytes()).unwrap();
let sizes = factory.generate_record_payload_sizes(2);
assert_eq!(sizes.len(), 3);
assert!(sizes[0] >= 400 && sizes[0] <= 500);
assert_eq!(sizes[1], CHECK_MARK);
assert!(sizes[2] >= 500 && sizes[2] <= 1000);
}
#[test]
fn test_beyond_stop() {
let factory = PaddingFactory::default();
let sizes = factory.generate_record_payload_sizes(10);
assert!(sizes.is_empty());
}
#[test]
fn test_md5_consistency() {
let factory1 = PaddingFactory::default();
let factory2 = PaddingFactory::new(DEFAULT_PADDING_SCHEME.as_bytes()).unwrap();
assert_eq!(factory1.md5(), factory2.md5());
}
#[test]
fn test_custom_scheme() {
let scheme = "stop=2\n0=100-100\n1=200-200";
let factory = PaddingFactory::new(scheme.as_bytes()).unwrap();
assert_eq!(factory.stop(), 2);
assert_eq!(factory.generate_record_payload_sizes(0), vec![100]);
assert_eq!(factory.generate_record_payload_sizes(1), vec![200]);
}
#[test]
fn test_invalid_scheme_missing_stop() {
let scheme = "0=100-100";
let result = PaddingFactory::new(scheme.as_bytes());
assert!(result.is_err());
}
}