From 84943f10eb5afdf3142642a2cba69833aa57d4e7 Mon Sep 17 00:00:00 2001 From: moznion Date: Wed, 25 Nov 2020 00:43:11 +0900 Subject: [PATCH] Add code generator for string attribute --- Cargo.toml | 3 +- Makefile | 3 + dicts/dictionary.rfc2865 | 138 +++++++++++++++++++++++++ src/attributes.rs | 38 ++++++- src/bin/code_gen.rs | 145 +++++++++++++++++++++++--- src/lib.rs | 2 + src/packet.rs | 19 +++- src/rfc2865.rs | 218 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 548 insertions(+), 18 deletions(-) create mode 100644 Makefile create mode 100644 dicts/dictionary.rfc2865 create mode 100644 src/rfc2865.rs diff --git a/Cargo.toml b/Cargo.toml index 77e8fb7..b66266c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,4 +16,5 @@ tokio = { version = "0.3.4", features = ["full"] } log = "0.4.11" thiserror = "1.0" regex = "1" -getopts = "0.2" +getopts = "0.2" # TODO for dev +Inflector = "0.11" # TODO for dev diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..993e50c --- /dev/null +++ b/Makefile @@ -0,0 +1,3 @@ +gen: + cargo run --bin code_gen $(PWD)/dicts/dictionary.rfc2865 $(PWD)/src/rfc2865.rs + cargo fmt diff --git a/dicts/dictionary.rfc2865 b/dicts/dictionary.rfc2865 new file mode 100644 index 0000000..8a2aedd --- /dev/null +++ b/dicts/dictionary.rfc2865 @@ -0,0 +1,138 @@ +# -*- text -*- +# Copyright (C) 2011 The FreeRADIUS Server project and contributors +# +# Attributes and values defined in RFC 2865. +# http://www.ietf.org/rfc/rfc2865.txt +# +# $Id$ +# +ATTRIBUTE User-Name 1 string +ATTRIBUTE User-Password 2 string encrypt=1 +ATTRIBUTE CHAP-Password 3 octets +ATTRIBUTE NAS-IP-Address 4 ipaddr +ATTRIBUTE NAS-Port 5 integer +ATTRIBUTE Service-Type 6 integer +ATTRIBUTE Framed-Protocol 7 integer +ATTRIBUTE Framed-IP-Address 8 ipaddr +ATTRIBUTE Framed-IP-Netmask 9 ipaddr +ATTRIBUTE Framed-Routing 10 integer +ATTRIBUTE Filter-Id 11 string +ATTRIBUTE Framed-MTU 12 integer +ATTRIBUTE Framed-Compression 13 integer +ATTRIBUTE Login-IP-Host 14 ipaddr +ATTRIBUTE Login-Service 15 integer +ATTRIBUTE Login-TCP-Port 16 integer +# Attribute 17 is undefined +ATTRIBUTE Reply-Message 18 string +ATTRIBUTE Callback-Number 19 string +ATTRIBUTE Callback-Id 20 string +# Attribute 21 is undefined +ATTRIBUTE Framed-Route 22 string +ATTRIBUTE Framed-IPX-Network 23 ipaddr +ATTRIBUTE State 24 octets +ATTRIBUTE Class 25 octets +ATTRIBUTE Vendor-Specific 26 vsa +ATTRIBUTE Session-Timeout 27 integer +ATTRIBUTE Idle-Timeout 28 integer +ATTRIBUTE Termination-Action 29 integer +ATTRIBUTE Called-Station-Id 30 string +ATTRIBUTE Calling-Station-Id 31 string +ATTRIBUTE NAS-Identifier 32 string +ATTRIBUTE Proxy-State 33 octets +ATTRIBUTE Login-LAT-Service 34 string +ATTRIBUTE Login-LAT-Node 35 string +ATTRIBUTE Login-LAT-Group 36 octets +ATTRIBUTE Framed-AppleTalk-Link 37 integer +ATTRIBUTE Framed-AppleTalk-Network 38 integer +ATTRIBUTE Framed-AppleTalk-Zone 39 string + +ATTRIBUTE CHAP-Challenge 60 octets +ATTRIBUTE NAS-Port-Type 61 integer +ATTRIBUTE Port-Limit 62 integer +ATTRIBUTE Login-LAT-Port 63 string + +# +# Integer Translations +# + +# Service types + +VALUE Service-Type Login-User 1 +VALUE Service-Type Framed-User 2 +VALUE Service-Type Callback-Login-User 3 +VALUE Service-Type Callback-Framed-User 4 +VALUE Service-Type Outbound-User 5 +VALUE Service-Type Administrative-User 6 +VALUE Service-Type NAS-Prompt-User 7 +VALUE Service-Type Authenticate-Only 8 +VALUE Service-Type Callback-NAS-Prompt 9 +VALUE Service-Type Call-Check 10 +VALUE Service-Type Callback-Administrative 11 + +# Framed Protocols + +VALUE Framed-Protocol PPP 1 +VALUE Framed-Protocol SLIP 2 +VALUE Framed-Protocol ARAP 3 +VALUE Framed-Protocol Gandalf-SLML 4 +VALUE Framed-Protocol Xylogics-IPX-SLIP 5 +VALUE Framed-Protocol X.75-Synchronous 6 + +# Framed Routing Values + +VALUE Framed-Routing None 0 +VALUE Framed-Routing Broadcast 1 +VALUE Framed-Routing Listen 2 +VALUE Framed-Routing Broadcast-Listen 3 + +# Framed Compression Types + +VALUE Framed-Compression None 0 +VALUE Framed-Compression Van-Jacobson-TCP-IP 1 +VALUE Framed-Compression IPX-Header-Compression 2 +VALUE Framed-Compression Stac-LZS 3 + +# Login Services + +VALUE Login-Service Telnet 0 +VALUE Login-Service Rlogin 1 +VALUE Login-Service TCP-Clear 2 +VALUE Login-Service PortMaster 3 +VALUE Login-Service LAT 4 +VALUE Login-Service X25-PAD 5 +VALUE Login-Service X25-T3POS 6 +VALUE Login-Service TCP-Clear-Quiet 8 + +# Login-TCP-Port (see /etc/services for more examples) + +VALUE Login-TCP-Port Telnet 23 +VALUE Login-TCP-Port Rlogin 513 +VALUE Login-TCP-Port Rsh 514 + +# Termination Options + +VALUE Termination-Action Default 0 +VALUE Termination-Action RADIUS-Request 1 + +# NAS Port Types + +VALUE NAS-Port-Type Async 0 +VALUE NAS-Port-Type Sync 1 +VALUE NAS-Port-Type ISDN 2 +VALUE NAS-Port-Type ISDN-V120 3 +VALUE NAS-Port-Type ISDN-V110 4 +VALUE NAS-Port-Type Virtual 5 +VALUE NAS-Port-Type PIAFS 6 +VALUE NAS-Port-Type HDLC-Clear-Channel 7 +VALUE NAS-Port-Type X.25 8 +VALUE NAS-Port-Type X.75 9 +VALUE NAS-Port-Type G.3-Fax 10 +VALUE NAS-Port-Type SDSL 11 +VALUE NAS-Port-Type ADSL-CAP 12 +VALUE NAS-Port-Type ADSL-DMT 13 +VALUE NAS-Port-Type IDSL 14 +VALUE NAS-Port-Type Ethernet 15 +VALUE NAS-Port-Type xDSL 16 +VALUE NAS-Port-Type Cable 17 +VALUE NAS-Port-Type Wireless-Other 18 +VALUE NAS-Port-Type Wireless-802.11 19 diff --git a/src/attributes.rs b/src/attributes.rs index b574ac1..63eae2b 100644 --- a/src/attributes.rs +++ b/src/attributes.rs @@ -1,12 +1,12 @@ use crate::attribute::Attribute; -pub type Type = u8; +pub type AVPType = u8; -pub const TYPE_INVALID: Type = 1; +pub const TYPE_INVALID: AVPType = 255; #[derive(Debug, Clone, PartialEq)] pub struct AVP { - typ: Type, + typ: AVPType, attribute: Attribute, } @@ -43,10 +43,40 @@ impl Attributes { Ok(Attributes(attrs)) } - pub fn add(&mut self, typ: Type, attribute: Attribute) { + pub(crate) fn add(&mut self, typ: AVPType, attribute: Attribute) { self.0.push(AVP { typ, attribute }) } + pub(crate) fn del(&mut self, typ: AVPType) { + self.0 = self + .0 + .iter() + .filter(|&avp| avp.typ != typ) + .cloned() + .collect(); + } + + pub(crate) fn lookup(&self, typ: AVPType) -> Option<&Attribute> { + self.0.iter().find_map(|avp| { + if avp.typ == typ { + return Some(&avp.attribute); + } + None + }) + } + + pub(crate) fn lookup_all(&self, typ: AVPType) -> Vec<&Attribute> { + self.0 + .iter() + .filter_map(|avp| { + if avp.typ == typ { + Some(&avp.attribute); + } + None + }) + .collect() + } + pub fn attributes_encoded_len(&self) -> Result { let mut n: u16 = 0; for attr in &self.0 { diff --git a/src/bin/code_gen.rs b/src/bin/code_gen.rs index 142ae79..dfcf7cc 100644 --- a/src/bin/code_gen.rs +++ b/src/bin/code_gen.rs @@ -1,10 +1,12 @@ use std::collections::HashMap; use std::fs::File; -use std::io::BufRead; +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"; @@ -13,15 +15,38 @@ const VALUE_KIND: &str = "VALUE"; #[derive(Debug)] struct RadiusAttribute { name: String, - identifier: u16, - typ: String, + typ: u8, + value_type: RadiusAttributeValueType, is_encrypt: bool, } #[derive(Debug)] struct RadiusValue { name: String, - identifier: u16, + typ: u16, +} + +#[derive(Debug)] +enum RadiusAttributeValueType { + STRING, + OCTETS, + IPADDR, + INTEGER, + VSA, +} + +impl FromStr for RadiusAttributeValueType { + type Err = (); + fn from_str(s: &str) -> Result { + match s.to_uppercase().as_str() { + "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) { @@ -47,15 +72,104 @@ fn main() { .parse(&args[1..]) .unwrap_or_else(|f| panic!(f.to_string())); - let dict_file_path = matches.free[0].clone(); + let dict_file_path = Path::new(&matches.free[0]); + if !dict_file_path.exists() { + panic!("no such dictionary file => {}", &matches.free[0]); + } + + let rfc_code = dict_file_path.extension().unwrap().to_str().unwrap(); + let struct_name = rfc_code.to_uppercase(); + let (radius_attributes, radius_attribute_to_values) = parse_dict_file(dict_file_path).unwrap(); - println!("{:?}", radius_attributes); - println!("{:?}", radius_attribute_to_values); + + let mut buf_writer = BufWriter::new(File::create(&matches.free[1]).unwrap()); + + generate_header(&mut buf_writer, &struct_name); + generate_attributes_code(&mut buf_writer, &radius_attributes); + generate_footer(&mut buf_writer); } +fn generate_header(w: &mut BufWriter, struct_name: &String) { + let code = format!( + "// Code generated by machine generator; DO NOT EDIT. + +use crate::attribute::Attribute; +use crate::attributes::AVPType; +use crate::packet::Packet; + +pub struct {struct_name} {{ +}} +impl {struct_name} {{ + ", + struct_name = struct_name + ); + + w.write_all(code.as_bytes()).unwrap(); +} + +fn generate_footer(w: &mut BufWriter) { + w.write_all(b"}\n").unwrap(); +} + +fn generate_attributes_code(w: &mut BufWriter, attrs: &[RadiusAttribute]) { + for attr in attrs { + generate_attribute_code(w, attr); + } +} + +fn generate_attribute_code(w: &mut BufWriter, attr: &RadiusAttribute) { + match attr.value_type { + RadiusAttributeValueType::STRING => generate_string_attribute_code(w, attr), + RadiusAttributeValueType::OCTETS => generate_octets_attribute_code(w, attr), + RadiusAttributeValueType::IPADDR => generate_ipaddr_attribute_code(w, attr), + RadiusAttributeValueType::INTEGER => generate_integer_attribute_code(w, attr), + RadiusAttributeValueType::VSA => generate_vsa_attribute_code(w, attr), + } +} + +fn generate_string_attribute_code(w: &mut BufWriter, attr: &RadiusAttribute) { + let attr_name = attr.name.clone(); + + let type_identifier = format!("{}_TYPE", attr_name.to_screaming_snake_case()); + let type_calling = format!("Self::{}", type_identifier); + + let code = format!( + "pub const {type_identifier}: AVPType = {type_value}; +pub fn add_{method_identifier}(packet: &mut Packet, value: &str) {{ + let attr = Attribute::from_string(value); + packet.add({type_calling}, &attr); +}} +pub fn delete_{method_identifier}(packet: &mut Packet) {{ + packet.delete({type_calling}); +}} +pub fn lookup_{method_identifier}(packet: &Packet) -> Option<&Attribute> {{ + packet.lookup({type_calling}) +}} +pub fn lookup_all_{method_identifier}(packet: &Packet) -> Vec<&Attribute> {{ + packet.lookup_all({type_calling}) +}} + +", + method_identifier = attr_name.to_snake_case(), + type_identifier = type_identifier, + type_calling = type_calling, + type_value = attr.typ, + ); + + w.write_all(code.as_bytes()).unwrap(); +} + +fn generate_octets_attribute_code(w: &mut BufWriter, attr: &RadiusAttribute) {} + +fn generate_ipaddr_attribute_code(w: &mut BufWriter, attr: &RadiusAttribute) {} + +fn generate_integer_attribute_code(w: &mut BufWriter, attr: &RadiusAttribute) {} + +fn generate_vsa_attribute_code(w: &mut BufWriter, attr: &RadiusAttribute) {} + type DictParsed = (Vec, HashMap>); -fn parse_dict_file(dict_file_path: String) -> Result { +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(); @@ -80,7 +194,14 @@ fn parse_dict_file(dict_file_path: String) -> Result { match kind { ATTRIBUTE_KIND => { let type_descriptions = items[3].split(' ').collect::>(); - let typ = type_descriptions[0].to_string(); + let typ = match RadiusAttributeValueType::from_str(type_descriptions[0]) { + Ok(t) => t, + Err(_) => { + return Err( + format!("invalid type has come => {}", type_descriptions[0]).to_owned() + ); + } + }; let is_encrypt = if type_descriptions.len() >= 2 { type_descriptions[1] == "encrypt=1" // FIXME: ad-hoc!!! } else { @@ -89,8 +210,8 @@ fn parse_dict_file(dict_file_path: String) -> Result { radius_attributes.push(RadiusAttribute { name: items[1].to_string(), - identifier: items[2].parse().unwrap(), - typ, + typ: items[2].parse().unwrap(), + value_type: typ, is_encrypt, }); } @@ -100,7 +221,7 @@ fn parse_dict_file(dict_file_path: String) -> Result { let radius_value = RadiusValue { name, - identifier: items[3].parse().unwrap(), + typ: items[3].parse().unwrap(), }; match radius_attribute_to_values.get_mut(&attribute_name) { diff --git a/src/lib.rs b/src/lib.rs index 05b12b6..2849cb4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +extern crate inflector; #[macro_use] extern crate log; @@ -8,5 +9,6 @@ pub mod code; pub mod packet; pub mod request; pub mod request_handler; +pub mod rfc2865; pub mod secret_provider; pub mod server; diff --git a/src/packet.rs b/src/packet.rs index 02a08a4..44cdd9f 100644 --- a/src/packet.rs +++ b/src/packet.rs @@ -2,7 +2,8 @@ use std::convert::TryInto; use rand::Rng; -use crate::attributes::Attributes; +use crate::attribute::Attribute; +use crate::attributes::{AVPType, Attributes}; use crate::code::Code; const MAX_PACKET_LENGTH: usize = 4096; @@ -177,6 +178,22 @@ impl Packet { _ => false, } } + + pub fn add(&mut self, typ: AVPType, attr: &Attribute) { + self.attributes.add(typ, attr.clone()); + } + + pub fn delete(&mut self, typ: AVPType) { + self.attributes.del(typ); + } + + pub fn lookup(&self, typ: AVPType) -> Option<&Attribute> { + self.attributes.lookup(typ) + } + + pub fn lookup_all(&self, typ: AVPType) -> Vec<&Attribute> { + self.attributes.lookup_all(typ) + } } #[cfg(test)] diff --git a/src/rfc2865.rs b/src/rfc2865.rs new file mode 100644 index 0000000..ab314c3 --- /dev/null +++ b/src/rfc2865.rs @@ -0,0 +1,218 @@ +// Code generated by machine generator; DO NOT EDIT. + +use crate::attribute::Attribute; +use crate::attributes::AVPType; +use crate::packet::Packet; + +pub struct RFC2865 {} +impl RFC2865 { + pub const USER_NAME_TYPE: AVPType = 1; + pub fn add_user_name(packet: &mut Packet, value: &str) { + let attr = Attribute::from_string(value); + packet.add(Self::USER_NAME_TYPE, &attr); + } + pub fn delete_user_name(packet: &mut Packet) { + packet.delete(Self::USER_NAME_TYPE); + } + pub fn lookup_user_name(packet: &Packet) -> Option<&Attribute> { + packet.lookup(Self::USER_NAME_TYPE) + } + pub fn lookup_all_user_name(packet: &Packet) -> Vec<&Attribute> { + packet.lookup_all(Self::USER_NAME_TYPE) + } + + pub const USER_PASSWORD_TYPE: AVPType = 2; + pub fn add_user_password(packet: &mut Packet, value: &str) { + let attr = Attribute::from_string(value); + packet.add(Self::USER_PASSWORD_TYPE, &attr); + } + pub fn delete_user_password(packet: &mut Packet) { + packet.delete(Self::USER_PASSWORD_TYPE); + } + pub fn lookup_user_password(packet: &Packet) -> Option<&Attribute> { + packet.lookup(Self::USER_PASSWORD_TYPE) + } + pub fn lookup_all_user_password(packet: &Packet) -> Vec<&Attribute> { + packet.lookup_all(Self::USER_PASSWORD_TYPE) + } + + pub const FILTER_ID_TYPE: AVPType = 11; + pub fn add_filter_id(packet: &mut Packet, value: &str) { + let attr = Attribute::from_string(value); + packet.add(Self::FILTER_ID_TYPE, &attr); + } + pub fn delete_filter_id(packet: &mut Packet) { + packet.delete(Self::FILTER_ID_TYPE); + } + pub fn lookup_filter_id(packet: &Packet) -> Option<&Attribute> { + packet.lookup(Self::FILTER_ID_TYPE) + } + pub fn lookup_all_filter_id(packet: &Packet) -> Vec<&Attribute> { + packet.lookup_all(Self::FILTER_ID_TYPE) + } + + pub const REPLY_MESSAGE_TYPE: AVPType = 18; + pub fn add_reply_message(packet: &mut Packet, value: &str) { + let attr = Attribute::from_string(value); + packet.add(Self::REPLY_MESSAGE_TYPE, &attr); + } + pub fn delete_reply_message(packet: &mut Packet) { + packet.delete(Self::REPLY_MESSAGE_TYPE); + } + pub fn lookup_reply_message(packet: &Packet) -> Option<&Attribute> { + packet.lookup(Self::REPLY_MESSAGE_TYPE) + } + pub fn lookup_all_reply_message(packet: &Packet) -> Vec<&Attribute> { + packet.lookup_all(Self::REPLY_MESSAGE_TYPE) + } + + pub const CALLBACK_NUMBER_TYPE: AVPType = 19; + pub fn add_callback_number(packet: &mut Packet, value: &str) { + let attr = Attribute::from_string(value); + packet.add(Self::CALLBACK_NUMBER_TYPE, &attr); + } + pub fn delete_callback_number(packet: &mut Packet) { + packet.delete(Self::CALLBACK_NUMBER_TYPE); + } + pub fn lookup_callback_number(packet: &Packet) -> Option<&Attribute> { + packet.lookup(Self::CALLBACK_NUMBER_TYPE) + } + pub fn lookup_all_callback_number(packet: &Packet) -> Vec<&Attribute> { + packet.lookup_all(Self::CALLBACK_NUMBER_TYPE) + } + + pub const CALLBACK_ID_TYPE: AVPType = 20; + pub fn add_callback_id(packet: &mut Packet, value: &str) { + let attr = Attribute::from_string(value); + packet.add(Self::CALLBACK_ID_TYPE, &attr); + } + pub fn delete_callback_id(packet: &mut Packet) { + packet.delete(Self::CALLBACK_ID_TYPE); + } + pub fn lookup_callback_id(packet: &Packet) -> Option<&Attribute> { + packet.lookup(Self::CALLBACK_ID_TYPE) + } + pub fn lookup_all_callback_id(packet: &Packet) -> Vec<&Attribute> { + packet.lookup_all(Self::CALLBACK_ID_TYPE) + } + + pub const FRAMED_ROUTE_TYPE: AVPType = 22; + pub fn add_framed_route(packet: &mut Packet, value: &str) { + let attr = Attribute::from_string(value); + packet.add(Self::FRAMED_ROUTE_TYPE, &attr); + } + pub fn delete_framed_route(packet: &mut Packet) { + packet.delete(Self::FRAMED_ROUTE_TYPE); + } + pub fn lookup_framed_route(packet: &Packet) -> Option<&Attribute> { + packet.lookup(Self::FRAMED_ROUTE_TYPE) + } + pub fn lookup_all_framed_route(packet: &Packet) -> Vec<&Attribute> { + packet.lookup_all(Self::FRAMED_ROUTE_TYPE) + } + + pub const CALLED_STATION_ID_TYPE: AVPType = 30; + pub fn add_called_station_id(packet: &mut Packet, value: &str) { + let attr = Attribute::from_string(value); + packet.add(Self::CALLED_STATION_ID_TYPE, &attr); + } + pub fn delete_called_station_id(packet: &mut Packet) { + packet.delete(Self::CALLED_STATION_ID_TYPE); + } + pub fn lookup_called_station_id(packet: &Packet) -> Option<&Attribute> { + packet.lookup(Self::CALLED_STATION_ID_TYPE) + } + pub fn lookup_all_called_station_id(packet: &Packet) -> Vec<&Attribute> { + packet.lookup_all(Self::CALLED_STATION_ID_TYPE) + } + + pub const CALLING_STATION_ID_TYPE: AVPType = 31; + pub fn add_calling_station_id(packet: &mut Packet, value: &str) { + let attr = Attribute::from_string(value); + packet.add(Self::CALLING_STATION_ID_TYPE, &attr); + } + pub fn delete_calling_station_id(packet: &mut Packet) { + packet.delete(Self::CALLING_STATION_ID_TYPE); + } + pub fn lookup_calling_station_id(packet: &Packet) -> Option<&Attribute> { + packet.lookup(Self::CALLING_STATION_ID_TYPE) + } + pub fn lookup_all_calling_station_id(packet: &Packet) -> Vec<&Attribute> { + packet.lookup_all(Self::CALLING_STATION_ID_TYPE) + } + + pub const NAS_IDENTIFIER_TYPE: AVPType = 32; + pub fn add_nas_identifier(packet: &mut Packet, value: &str) { + let attr = Attribute::from_string(value); + packet.add(Self::NAS_IDENTIFIER_TYPE, &attr); + } + pub fn delete_nas_identifier(packet: &mut Packet) { + packet.delete(Self::NAS_IDENTIFIER_TYPE); + } + pub fn lookup_nas_identifier(packet: &Packet) -> Option<&Attribute> { + packet.lookup(Self::NAS_IDENTIFIER_TYPE) + } + pub fn lookup_all_nas_identifier(packet: &Packet) -> Vec<&Attribute> { + packet.lookup_all(Self::NAS_IDENTIFIER_TYPE) + } + + pub const LOGIN_LAT_SERVICE_TYPE: AVPType = 34; + pub fn add_login_lat_service(packet: &mut Packet, value: &str) { + let attr = Attribute::from_string(value); + packet.add(Self::LOGIN_LAT_SERVICE_TYPE, &attr); + } + pub fn delete_login_lat_service(packet: &mut Packet) { + packet.delete(Self::LOGIN_LAT_SERVICE_TYPE); + } + pub fn lookup_login_lat_service(packet: &Packet) -> Option<&Attribute> { + packet.lookup(Self::LOGIN_LAT_SERVICE_TYPE) + } + pub fn lookup_all_login_lat_service(packet: &Packet) -> Vec<&Attribute> { + packet.lookup_all(Self::LOGIN_LAT_SERVICE_TYPE) + } + + pub const LOGIN_LAT_NODE_TYPE: AVPType = 35; + pub fn add_login_lat_node(packet: &mut Packet, value: &str) { + let attr = Attribute::from_string(value); + packet.add(Self::LOGIN_LAT_NODE_TYPE, &attr); + } + pub fn delete_login_lat_node(packet: &mut Packet) { + packet.delete(Self::LOGIN_LAT_NODE_TYPE); + } + pub fn lookup_login_lat_node(packet: &Packet) -> Option<&Attribute> { + packet.lookup(Self::LOGIN_LAT_NODE_TYPE) + } + pub fn lookup_all_login_lat_node(packet: &Packet) -> Vec<&Attribute> { + packet.lookup_all(Self::LOGIN_LAT_NODE_TYPE) + } + + pub const FRAMED_APPLE_TALK_ZONE_TYPE: AVPType = 39; + pub fn add_framed_apple_talk_zone(packet: &mut Packet, value: &str) { + let attr = Attribute::from_string(value); + packet.add(Self::FRAMED_APPLE_TALK_ZONE_TYPE, &attr); + } + pub fn delete_framed_apple_talk_zone(packet: &mut Packet) { + packet.delete(Self::FRAMED_APPLE_TALK_ZONE_TYPE); + } + pub fn lookup_framed_apple_talk_zone(packet: &Packet) -> Option<&Attribute> { + packet.lookup(Self::FRAMED_APPLE_TALK_ZONE_TYPE) + } + pub fn lookup_all_framed_apple_talk_zone(packet: &Packet) -> Vec<&Attribute> { + packet.lookup_all(Self::FRAMED_APPLE_TALK_ZONE_TYPE) + } + + pub const LOGIN_LAT_PORT_TYPE: AVPType = 63; + pub fn add_login_lat_port(packet: &mut Packet, value: &str) { + let attr = Attribute::from_string(value); + packet.add(Self::LOGIN_LAT_PORT_TYPE, &attr); + } + pub fn delete_login_lat_port(packet: &mut Packet) { + packet.delete(Self::LOGIN_LAT_PORT_TYPE); + } + pub fn lookup_login_lat_port(packet: &Packet) -> Option<&Attribute> { + packet.lookup(Self::LOGIN_LAT_PORT_TYPE) + } + pub fn lookup_all_login_lat_port(packet: &Packet) -> Vec<&Attribute> { + packet.lookup_all(Self::LOGIN_LAT_PORT_TYPE) + } +}