use std::collections::{BTreeMap, HashSet}; use std::fs::File; use std::io::{BufRead, BufWriter, Write}; use std::path::Path; use std::str::FromStr; use std::{env, io, process}; use getopts::Options; use inflector::Inflector; use regex::Regex; const ATTRIBUTE_KIND: &str = "ATTRIBUTE"; const VALUE_KIND: &str = "VALUE"; const RADIUS_VALUE_TYPE: &str = "u32"; const USER_PASSWORD_TYPE_OPT: &str = "encrypt=1"; const TUNNEL_PASSWORD_TYPE_OPT: &str = "encrypt=2"; const HAS_TAG_TYPE_OPT: &str = "has_tag"; #[derive(Debug)] enum EncryptionType { UserPassword, TunnelPassword, } #[derive(Debug)] struct RadiusAttribute { name: String, typ: u8, value_type: RadiusAttributeValueType, has_tag: bool, } #[derive(Debug)] struct RadiusValue { name: String, value: u16, } #[derive(Debug, PartialEq)] enum RadiusAttributeValueType { String, UserPassword, TunnelPassword, Octets, IpAddr, Integer, VSA, } impl FromStr for RadiusAttributeValueType { type Err = (); fn from_str(s: &str) -> Result { match s { "string" => Ok(RadiusAttributeValueType::String), "octets" => Ok(RadiusAttributeValueType::Octets), "ipaddr" => Ok(RadiusAttributeValueType::IpAddr), "integer" => Ok(RadiusAttributeValueType::Integer), "vsa" => Ok(RadiusAttributeValueType::VSA), _ => Err(()), } } } fn print_usage(program: &str, opts: &Options) { let brief = format!("Usage: {} [options] DICT_FILE OUT_FILE", program); print!("{}", opts.usage(&brief)); process::exit(0); } fn read_lines

(filename: P) -> io::Result>> where P: AsRef, { let file = File::open(filename)?; Ok(io::BufReader::new(file).lines()) } fn main() { let args: Vec = env::args().collect(); let program = args[0].clone(); let mut opts = Options::new(); opts.optflag("h", "help", "print this help menu"); let matches = opts .parse(&args[1..]) .unwrap_or_else(|f| panic!(f.to_string())); if matches.opt_present("h") { print_usage(&program, &opts); } let dict_file_path = Path::new(&matches.free[0]); if !dict_file_path.exists() { panic!("no such dictionary file => {}", &matches.free[0]); } let (radius_attributes, radius_attribute_to_values_map) = parse_dict_file(dict_file_path).unwrap(); let value_defined_attributes_set = radius_attribute_to_values_map .keys() .collect::>(); let mut w = BufWriter::new(File::create(&matches.free[1]).unwrap()); generate_header(&mut w); generate_values_code(&mut w, &radius_attribute_to_values_map); generate_attributes_code(&mut w, &radius_attributes, &value_defined_attributes_set); } fn generate_header(w: &mut BufWriter) { let code = b"// Code generated by machine generator; DO NOT EDIT. use std::net::Ipv4Addr; use crate::avp::{AVP, AVPType, AVPError}; use crate::packet::Packet; use crate::tag::Tag; "; w.write_all(code).unwrap(); } fn generate_values_code( w: &mut BufWriter, attr_to_values_map: &BTreeMap>, ) { for (attr, values) in attr_to_values_map { generate_values_for_attribute_code(w, attr, values); } } fn generate_values_for_attribute_code(w: &mut BufWriter, attr: &str, values: &[RadiusValue]) { let type_name = attr.to_pascal_case(); w.write_all( format!( "\npub type {type_name} = {radius_value_type};\n", type_name = type_name, radius_value_type = RADIUS_VALUE_TYPE ) .as_bytes(), ) .unwrap(); for v in values { w.write_all( format!( "pub const {type_name_prefix}_{value_name}: {type_name} = {value};\n", type_name_prefix = type_name.to_screaming_snake_case(), value_name = v.name.to_screaming_snake_case(), type_name = type_name, value = v.value, ) .as_bytes(), ) .unwrap(); } w.write_all(b"\n").unwrap(); } fn generate_attributes_code( w: &mut BufWriter, attrs: &[RadiusAttribute], value_defined_attributes_set: &HashSet<&String>, ) { for attr in attrs { generate_attribute_code(w, attr, &value_defined_attributes_set); } } fn generate_attribute_code( w: &mut BufWriter, attr: &RadiusAttribute, value_defined_attributes_set: &HashSet<&String>, ) { let attr_name = attr.name.clone(); let type_identifier = format!("{}_TYPE", attr_name.to_screaming_snake_case()); let type_value = attr.typ; let method_identifier = attr_name.to_snake_case(); generate_common_attribute_code(w, &attr_name, &type_identifier, type_value); match attr.value_type { RadiusAttributeValueType::String => match attr.has_tag { true => generate_tagged_string_attribute_code(w, &method_identifier, &type_identifier), false => generate_string_attribute_code(w, &method_identifier, &type_identifier), }, RadiusAttributeValueType::UserPassword => match attr.has_tag { true => unimplemented!(), false => generate_user_password_attribute_code(w, &method_identifier, &type_identifier), }, RadiusAttributeValueType::TunnelPassword => match attr.has_tag { true => { generate_tunnel_password_attribute_code(w, &method_identifier, &type_identifier) } false => unimplemented!(), }, RadiusAttributeValueType::Octets => match attr.has_tag { true => unimplemented!(), false => generate_octets_attribute_code(w, &method_identifier, &type_identifier), }, RadiusAttributeValueType::IpAddr => match attr.has_tag { true => unimplemented!(), false => generate_ipaddr_attribute_code(w, &method_identifier, &type_identifier), }, RadiusAttributeValueType::Integer => { match value_defined_attributes_set.contains(&attr_name) { true => match attr.has_tag { true => generate_value_tagged_defined_integer_attribute_code( w, &method_identifier, &type_identifier, &attr_name.to_pascal_case(), ), false => generate_value_defined_integer_attribute_code( w, &method_identifier, &type_identifier, &attr_name.to_pascal_case(), ), }, false => match attr.has_tag { true => generate_tagged_integer_attribute_code( w, &method_identifier, &type_identifier, ), false => { generate_integer_attribute_code(w, &method_identifier, &type_identifier) } }, } } RadiusAttributeValueType::VSA => generate_vsa_attribute_code(), } } fn generate_common_attribute_code( w: &mut BufWriter, attr_name: &str, type_identifier: &str, type_value: u8, ) { let code = format!( " pub const {type_identifier}: AVPType = {type_value}; pub fn delete_{method_identifier}(packet: &mut Packet) {{ packet.delete({type_identifier}); }} ", method_identifier = attr_name.to_snake_case(), type_identifier = type_identifier, type_value = type_value, ); w.write_all(code.as_bytes()).unwrap(); } fn generate_string_attribute_code( w: &mut BufWriter, method_identifier: &str, type_identifier: &str, ) { let code = format!( "pub fn add_{method_identifier}(packet: &mut Packet, value: &str) {{ packet.add(AVP::encode_string({type_identifier}, value)); }} pub fn lookup_{method_identifier}(packet: &Packet) -> Option> {{ packet.lookup({type_identifier}).map(|v| v.decode_string()) }} pub fn lookup_all_{method_identifier}(packet: &Packet) -> Result, AVPError> {{ let mut vec = Vec::new(); for avp in packet.lookup_all({type_identifier}) {{ vec.push(avp.decode_string()?) }} Ok(vec) }} ", method_identifier = method_identifier, type_identifier = type_identifier, ); w.write_all(code.as_bytes()).unwrap(); } fn generate_tagged_string_attribute_code( w: &mut BufWriter, method_identifier: &str, type_identifier: &str, ) { let code = format!( "pub fn add_{method_identifier}(packet: &mut Packet, tag: Option<&Tag>, value: &str) {{ packet.add(AVP::encode_tagged_string({type_identifier}, tag, value)); }} pub fn lookup_{method_identifier}(packet: &Packet) -> Option), AVPError>> {{ packet.lookup({type_identifier}).map(|v| v.decode_tagged_string()) }} pub fn lookup_all_{method_identifier}(packet: &Packet) -> Result)>, AVPError> {{ let mut vec = Vec::new(); for avp in packet.lookup_all({type_identifier}) {{ vec.push(avp.decode_tagged_string()?) }} Ok(vec) }} ", method_identifier = method_identifier, type_identifier = type_identifier, ); w.write_all(code.as_bytes()).unwrap(); } fn generate_user_password_attribute_code( w: &mut BufWriter, method_identifier: &str, type_identifier: &str, ) { let code = format!( "pub fn add_{method_identifier}(packet: &mut Packet, value: &[u8]) -> Result<(), AVPError> {{ packet.add(AVP::encode_user_password({type_identifier}, value, packet.get_secret(), packet.get_authenticator())?); Ok(()) }} pub fn lookup_{method_identifier}(packet: &Packet) -> Option, AVPError>> {{ packet.lookup({type_identifier}).map(|v| v.decode_user_password(packet.get_secret(), packet.get_authenticator())) }} pub fn lookup_all_{method_identifier}(packet: &Packet) -> Result>, AVPError> {{ let mut vec = Vec::new(); for avp in packet.lookup_all({type_identifier}) {{ vec.push(avp.decode_user_password(packet.get_secret(), packet.get_authenticator())?) }} Ok(vec) }} ", method_identifier = method_identifier, type_identifier = type_identifier, ); w.write_all(code.as_bytes()).unwrap(); } fn generate_tunnel_password_attribute_code( w: &mut BufWriter, method_identifier: &str, type_identifier: &str, ) { let code = format!( "pub fn add_{method_identifier}(packet: &mut Packet, tag: Option<&Tag>, value: &[u8]) -> Result<(), AVPError> {{ packet.add(AVP::encode_tunnel_password({type_identifier}, tag, value, packet.get_secret(), packet.get_authenticator())?); Ok(()) }} pub fn lookup_{method_identifier}(packet: &Packet) -> Option, Tag), AVPError>> {{ packet.lookup({type_identifier}).map(|v| v.decode_tunnel_password(packet.get_secret(), packet.get_authenticator())) }} pub fn lookup_all_{method_identifier}(packet: &Packet) -> Result, Tag)>, AVPError> {{ let mut vec = Vec::new(); for avp in packet.lookup_all({type_identifier}) {{ vec.push(avp.decode_tunnel_password(packet.get_secret(), packet.get_authenticator())?) }} Ok(vec) }} ", method_identifier = method_identifier, type_identifier = type_identifier, ); w.write_all(code.as_bytes()).unwrap(); } fn generate_octets_attribute_code( w: &mut BufWriter, method_identifier: &str, type_identifier: &str, ) { let code = format!( "pub fn add_{method_identifier}(packet: &mut Packet, value: &[u8]) {{ packet.add(AVP::encode_bytes({type_identifier}, value)); }} pub fn lookup_{method_identifier}(packet: &Packet) -> Option> {{ packet.lookup({type_identifier}).map(|v| v.decode_bytes()) }} pub fn lookup_all_{method_identifier}(packet: &Packet) -> Vec> {{ let mut vec = Vec::new(); for avp in packet.lookup_all({type_identifier}) {{ vec.push(avp.decode_bytes()) }} vec }} ", method_identifier = method_identifier, type_identifier = type_identifier, ); w.write_all(code.as_bytes()).unwrap(); } fn generate_ipaddr_attribute_code( w: &mut BufWriter, method_identifier: &str, type_identifier: &str, ) { let code = format!( "pub fn add_{method_identifier}(packet: &mut Packet, value: &Ipv4Addr) {{ packet.add(AVP::encode_ipv4({type_identifier}, value)); }} pub fn lookup_{method_identifier}(packet: &Packet) -> Option> {{ packet.lookup({type_identifier}).map(|v| v.decode_ipv4()) }} pub fn lookup_all_{method_identifier}(packet: &Packet) -> Result, AVPError> {{ let mut vec = Vec::new(); for avp in packet.lookup_all({type_identifier}) {{ vec.push(avp.decode_ipv4()?) }} Ok(vec) }} ", method_identifier = method_identifier, type_identifier = type_identifier, ); w.write_all(code.as_bytes()).unwrap(); } fn generate_integer_attribute_code( w: &mut BufWriter, method_identifier: &str, type_identifier: &str, ) { let code = format!( "pub fn add_{method_identifier}(packet: &mut Packet, value: u32) {{ packet.add(AVP::encode_u32({type_identifier}, value)); }} pub fn lookup_{method_identifier}(packet: &Packet) -> Option> {{ packet.lookup({type_identifier}).map(|v| v.decode_u32()) }} pub fn lookup_all_{method_identifier}(packet: &Packet) -> Result, AVPError> {{ let mut vec = Vec::new(); for avp in packet.lookup_all({type_identifier}) {{ vec.push(avp.decode_u32()?) }} Ok(vec) }} ", method_identifier = method_identifier, type_identifier = type_identifier, ); w.write_all(code.as_bytes()).unwrap(); } fn generate_tagged_integer_attribute_code( w: &mut BufWriter, method_identifier: &str, type_identifier: &str, ) { let code = format!( "pub fn add_{method_identifier}(packet: &mut Packet, tag: Option<&Tag>, value: u32) {{ packet.add(AVP::encode_tagged_u32({type_identifier}, tag, value)); }} pub fn lookup_{method_identifier}(packet: &Packet) -> Option> {{ packet.lookup({type_identifier}).map(|v| v.decode_tagged_u32()) }} pub fn lookup_all_{method_identifier}(packet: &Packet) -> Result, AVPError> {{ let mut vec = Vec::new(); for avp in packet.lookup_all({type_identifier}) {{ vec.push(avp.decode_tagged_u32()?) }} Ok(vec) }} ", method_identifier = method_identifier, type_identifier = type_identifier, ); w.write_all(code.as_bytes()).unwrap(); } fn generate_value_defined_integer_attribute_code( w: &mut BufWriter, method_identifier: &str, type_identifier: &str, value_type: &str, ) { let code = format!( "pub fn add_{method_identifier}(packet: &mut Packet, value: {value_type}) {{ packet.add(AVP::encode_u32({type_identifier}, value as u32)); }} pub fn lookup_{method_identifier}(packet: &Packet) -> Option> {{ packet.lookup({type_identifier}).map(|v| Ok(v.decode_u32()? as {value_type})) }} pub fn lookup_all_{method_identifier}(packet: &Packet) -> Result, AVPError> {{ let mut vec = Vec::new(); for avp in packet.lookup_all({type_identifier}) {{ vec.push(avp.decode_u32()? as {value_type}) }} Ok(vec) }} ", method_identifier = method_identifier, type_identifier = type_identifier, value_type = value_type, ); w.write_all(code.as_bytes()).unwrap(); } fn generate_value_tagged_defined_integer_attribute_code( w: &mut BufWriter, method_identifier: &str, type_identifier: &str, value_type: &str, ) { let code = format!( "pub fn add_{method_identifier}(packet: &mut Packet, tag: Option<&Tag>, value: {value_type}) {{ packet.add(AVP::encode_tagged_u32({type_identifier}, tag, value as u32)); }} pub fn lookup_{method_identifier}(packet: &Packet) -> Option> {{ packet.lookup({type_identifier}).map(|v| {{ let (v, t) = v.decode_tagged_u32()?; Ok((v as {value_type}, t)) }}) }} pub fn lookup_all_{method_identifier}(packet: &Packet) -> Result, AVPError> {{ let mut vec = Vec::new(); for avp in packet.lookup_all({type_identifier}) {{ let (v, t) = avp.decode_tagged_u32()?; vec.push((v as {value_type}, t)) }} Ok(vec) }} ", method_identifier = method_identifier, type_identifier = type_identifier, value_type = value_type, ); w.write_all(code.as_bytes()).unwrap(); } fn generate_vsa_attribute_code() { // NOP } type DictParsed = (Vec, BTreeMap>); fn parse_dict_file(dict_file_path: &Path) -> Result { let line_filter_re = Regex::new(r"^(?:#.*|)$").unwrap(); let tabs_re = Regex::new(r"\t+").unwrap(); let trailing_comment_re = Regex::new(r"\s*?#.+?$").unwrap(); let mut radius_attributes: Vec = Vec::new(); let mut radius_attribute_to_values: BTreeMap> = BTreeMap::new(); let lines = read_lines(dict_file_path).unwrap(); for line_result in lines { let line = line_result.unwrap(); if line_filter_re.is_match(line.as_str()) { continue; } let items = tabs_re.split(line.as_str()).collect::>(); if items.len() < 4 { return Err("the number of items is lacked in a line".to_owned()); } let kind = items[0]; match kind { ATTRIBUTE_KIND => { let mut encryption_type: Option = None; let mut has_tag = false; if items.len() >= 5 { // TODO consider to extract to a method for type_opt in items[4].split(',') { if type_opt == USER_PASSWORD_TYPE_OPT { encryption_type = Some(EncryptionType::UserPassword); continue; } if type_opt == TUNNEL_PASSWORD_TYPE_OPT { encryption_type = Some(EncryptionType::TunnelPassword); continue; } if type_opt == HAS_TAG_TYPE_OPT { has_tag = true; continue; } } } let typ = match RadiusAttributeValueType::from_str(items[3]) { Ok(t) => { if t == RadiusAttributeValueType::String { match encryption_type { Some(EncryptionType::UserPassword) => { RadiusAttributeValueType::UserPassword } Some(EncryptionType::TunnelPassword) => { RadiusAttributeValueType::TunnelPassword } None => t, } } else { t } } Err(_) => { return Err(format!("invalid type has come => {}", items[3])); } }; radius_attributes.push(RadiusAttribute { name: items[1].to_string(), typ: items[2].parse().unwrap(), value_type: typ, has_tag, }); } VALUE_KIND => { let attribute_name = items[1].to_string(); let name = items[2].to_string(); let value = trailing_comment_re.replace(items[3], "").to_string(); let radius_value = RadiusValue { name, value: value.parse().unwrap(), }; match radius_attribute_to_values.get_mut(&attribute_name) { None => { radius_attribute_to_values .insert(attribute_name.clone(), vec![radius_value]); } Some(vec) => { vec.push(radius_value); } }; } _ => return Err(format!("unexpected kind has come => {}", kind)), } } Ok((radius_attributes, radius_attribute_to_values)) }