Files
Stalwart/crates/common/src/config/smtp/session.rs

862 lines
29 KiB
Rust

use std::{
net::{SocketAddr, ToSocketAddrs},
time::Duration,
};
use smtp_proto::*;
use utils::config::{utils::ParseValue, Config};
use crate::{
config::CONNECTION_VARS,
expr::{if_block::IfBlock, tokenizer::TokenMap, *},
};
use self::{resolver::Policy, throttle::parse_throttle};
use super::*;
#[derive(Clone)]
pub struct SessionConfig {
pub timeout: IfBlock,
pub duration: IfBlock,
pub transfer_limit: IfBlock,
pub throttle: SessionThrottle,
pub connect: Connect,
pub ehlo: Ehlo,
pub auth: Auth,
pub mail: Mail,
pub rcpt: Rcpt,
pub data: Data,
pub extensions: Extensions,
pub mta_sts_policy: Option<Policy>,
}
#[derive(Default, Debug, Clone)]
pub struct SessionThrottle {
pub connect: Vec<Throttle>,
pub mail_from: Vec<Throttle>,
pub rcpt_to: Vec<Throttle>,
}
#[derive(Clone)]
pub struct Connect {
pub hostname: IfBlock,
pub script: IfBlock,
pub greeting: IfBlock,
}
#[derive(Clone)]
pub struct Ehlo {
pub script: IfBlock,
pub require: IfBlock,
pub reject_non_fqdn: IfBlock,
}
#[derive(Clone)]
pub struct Extensions {
pub pipelining: IfBlock,
pub chunking: IfBlock,
pub requiretls: IfBlock,
pub dsn: IfBlock,
pub vrfy: IfBlock,
pub expn: IfBlock,
pub no_soliciting: IfBlock,
pub future_release: IfBlock,
pub deliver_by: IfBlock,
pub mt_priority: IfBlock,
}
#[derive(Clone)]
pub struct Auth {
pub directory: IfBlock,
pub mechanisms: IfBlock,
pub require: IfBlock,
pub must_match_sender: IfBlock,
pub errors_max: IfBlock,
pub errors_wait: IfBlock,
}
#[derive(Clone)]
pub struct Mail {
pub script: IfBlock,
pub rewrite: IfBlock,
}
#[derive(Clone)]
pub struct Rcpt {
pub script: IfBlock,
pub relay: IfBlock,
pub directory: IfBlock,
pub rewrite: IfBlock,
// Errors
pub errors_max: IfBlock,
pub errors_wait: IfBlock,
// Limits
pub max_recipients: IfBlock,
// Catch-all and sub-adressing
pub catch_all: AddressMapping,
pub subaddressing: AddressMapping,
}
#[derive(Debug, Default, Clone)]
pub enum AddressMapping {
Enable,
Custom(IfBlock),
#[default]
Disable,
}
#[derive(Clone)]
pub struct Data {
pub script: IfBlock,
pub pipe_commands: Vec<Pipe>,
pub milters: Vec<Milter>,
// Limits
pub max_messages: IfBlock,
pub max_message_size: IfBlock,
pub max_received_headers: IfBlock,
// Headers
pub add_received: IfBlock,
pub add_received_spf: IfBlock,
pub add_return_path: IfBlock,
pub add_auth_results: IfBlock,
pub add_message_id: IfBlock,
pub add_date: IfBlock,
}
// Ceci n'est pas une pipe
#[derive(Clone)]
pub struct Pipe {
pub command: IfBlock,
pub arguments: IfBlock,
pub timeout: IfBlock,
}
#[derive(Clone)]
pub struct Milter {
pub enable: IfBlock,
pub addrs: Vec<SocketAddr>,
pub hostname: String,
pub port: u16,
pub timeout_connect: Duration,
pub timeout_command: Duration,
pub timeout_data: Duration,
pub tls: bool,
pub tls_allow_invalid_certs: bool,
pub tempfail_on_error: bool,
pub max_frame_len: usize,
pub protocol_version: MilterVersion,
pub flags_actions: Option<u32>,
pub flags_protocol: Option<u32>,
}
#[derive(Clone, Copy)]
pub enum MilterVersion {
V2,
V6,
}
impl SessionConfig {
pub fn parse(config: &mut Config) -> Self {
let has_conn_vars = TokenMap::default().with_variables(CONNECTION_VARS);
let has_ehlo_hars = TokenMap::default().with_variables(SMTP_EHLO_VARS);
let has_sender_vars = TokenMap::default().with_variables(SMTP_MAIL_FROM_VARS);
let has_rcpt_vars = TokenMap::default().with_variables(SMTP_RCPT_TO_VARS);
let mt_priority_vars = has_sender_vars.clone().with_constants::<MtPriority>();
let mechanisms_vars = has_ehlo_hars.clone().with_constants::<Mechanism>();
let mut session = SessionConfig::default();
session.rcpt.catch_all = AddressMapping::parse(config, "session.rcpt.catch-all");
session.rcpt.subaddressing = AddressMapping::parse(config, "session.rcpt.sub-addressing");
session.data.milters = config
.sub_keys("session.data.milter", "")
.map(|s| s.to_string())
.collect::<Vec<_>>()
.into_iter()
.filter_map(|id| parse_milter(config, &id, &has_rcpt_vars))
.collect();
session.data.pipe_commands = config
.sub_keys("session.data.pipe", "")
.map(|s| s.to_string())
.collect::<Vec<_>>()
.into_iter()
.filter_map(|id| parse_pipe(config, &id, &has_rcpt_vars))
.collect();
session.throttle = SessionThrottle::parse(config);
session.mta_sts_policy = Policy::try_parse(config);
for (value, key, token_map) in [
(&mut session.duration, "session.duration", &has_conn_vars),
(
&mut session.transfer_limit,
"session.transfer-limit",
&has_conn_vars,
),
(&mut session.timeout, "session.timeout", &has_conn_vars),
(
&mut session.connect.script,
"session.connect.script",
&has_conn_vars,
),
(
&mut session.connect.hostname,
"session.connect.hostname",
&has_conn_vars,
),
(
&mut session.connect.greeting,
"session.connect.greeting",
&has_conn_vars,
),
(
&mut session.extensions.pipelining,
"session.extensions.pipelining",
&has_sender_vars,
),
(
&mut session.extensions.dsn,
"session.extensions.dsn",
&has_sender_vars,
),
(
&mut session.extensions.vrfy,
"session.extensions.vrfy",
&has_sender_vars,
),
(
&mut session.extensions.expn,
"session.extensions.expn",
&has_sender_vars,
),
(
&mut session.extensions.chunking,
"session.extensions.chunking",
&has_sender_vars,
),
(
&mut session.extensions.requiretls,
"session.extensions.requiretls",
&has_sender_vars,
),
(
&mut session.extensions.no_soliciting,
"session.extensions.no-soliciting",
&has_sender_vars,
),
(
&mut session.extensions.future_release,
"session.extensions.future-release",
&has_sender_vars,
),
(
&mut session.extensions.deliver_by,
"session.extensions.deliver-by",
&has_sender_vars,
),
(
&mut session.extensions.mt_priority,
"session.extensions.mt-priority",
&mt_priority_vars,
),
(
&mut session.ehlo.script,
"session.ehlo.script",
&has_conn_vars,
),
(
&mut session.ehlo.require,
"session.ehlo.require",
&has_conn_vars,
),
(
&mut session.ehlo.reject_non_fqdn,
"session.ehlo.reject-non-fqdn",
&has_conn_vars,
),
(
&mut session.auth.directory,
"session.auth.directory",
&has_ehlo_hars,
),
(
&mut session.auth.mechanisms,
"session.auth.mechanisms",
&mechanisms_vars,
),
(
&mut session.auth.require,
"session.auth.require",
&has_ehlo_hars,
),
(
&mut session.auth.errors_max,
"session.auth.errors.total",
&has_ehlo_hars,
),
(
&mut session.auth.errors_wait,
"session.auth.errors.wait",
&has_ehlo_hars,
),
(
&mut session.auth.must_match_sender,
"session.auth.must-match-sender",
&has_sender_vars,
),
(
&mut session.mail.script,
"session.mail.script",
&has_sender_vars,
),
(
&mut session.mail.rewrite,
"session.mail.rewrite",
&has_sender_vars,
),
(
&mut session.rcpt.script,
"session.rcpt.script",
&has_rcpt_vars,
),
(
&mut session.rcpt.relay,
"session.rcpt.relay",
&has_rcpt_vars,
),
(
&mut session.rcpt.directory,
"session.rcpt.directory",
&has_rcpt_vars,
),
(
&mut session.rcpt.errors_max,
"session.rcpt.errors.total",
&has_sender_vars,
),
(
&mut session.rcpt.errors_wait,
"session.rcpt.errors.wait",
&has_sender_vars,
),
(
&mut session.rcpt.max_recipients,
"session.rcpt.max-recipients",
&has_sender_vars,
),
(
&mut session.rcpt.rewrite,
"session.rcpt.rewrite",
&has_rcpt_vars,
),
(
&mut session.data.script,
"session.data.script",
&has_rcpt_vars,
),
(
&mut session.data.max_messages,
"session.data.limits.messages",
&has_rcpt_vars,
),
(
&mut session.data.max_message_size,
"session.data.limits.size",
&has_rcpt_vars,
),
(
&mut session.data.max_received_headers,
"session.data.limits.received-headers",
&has_rcpt_vars,
),
(
&mut session.data.add_received,
"session.data.add-headers.received",
&has_rcpt_vars,
),
(
&mut session.data.add_received_spf,
"session.data.add-headers.received-spf",
&has_rcpt_vars,
),
(
&mut session.data.add_return_path,
"session.data.add-headers.return-path",
&has_rcpt_vars,
),
(
&mut session.data.add_auth_results,
"session.data.add-headers.auth-results",
&has_rcpt_vars,
),
(
&mut session.data.add_message_id,
"session.data.add-headers.message-id",
&has_rcpt_vars,
),
(
&mut session.data.add_date,
"session.data.add-headers.date",
&has_rcpt_vars,
),
] {
if let Some(if_block) = IfBlock::try_parse(config, key, token_map) {
*value = if_block;
}
}
session
}
}
impl SessionThrottle {
pub fn parse(config: &mut Config) -> Self {
let mut throttle = SessionThrottle::default();
let all_throttles = parse_throttle(
config,
"session.throttle",
&TokenMap::default().with_variables(SMTP_RCPT_TO_VARS),
THROTTLE_LISTENER
| THROTTLE_REMOTE_IP
| THROTTLE_LOCAL_IP
| THROTTLE_AUTH_AS
| THROTTLE_HELO_DOMAIN
| THROTTLE_RCPT
| THROTTLE_RCPT_DOMAIN
| THROTTLE_SENDER
| THROTTLE_SENDER_DOMAIN,
);
for t in all_throttles {
if (t.keys & (THROTTLE_RCPT | THROTTLE_RCPT_DOMAIN)) != 0
|| t.expr.items().iter().any(|c| {
matches!(
c,
ExpressionItem::Variable(V_RECIPIENT | V_RECIPIENT_DOMAIN)
)
})
{
throttle.rcpt_to.push(t);
} else if (t.keys
& (THROTTLE_SENDER
| THROTTLE_SENDER_DOMAIN
| THROTTLE_HELO_DOMAIN
| THROTTLE_AUTH_AS))
!= 0
|| t.expr.items().iter().any(|c| {
matches!(
c,
ExpressionItem::Variable(
V_SENDER | V_SENDER_DOMAIN | V_HELO_DOMAIN | V_AUTHENTICATED_AS
)
)
})
{
throttle.mail_from.push(t);
} else {
throttle.connect.push(t);
}
}
throttle
}
}
fn parse_pipe(config: &mut Config, id: &str, token_map: &TokenMap) -> Option<Pipe> {
Some(Pipe {
command: IfBlock::try_parse(config, ("session.data.pipe", id, "command"), token_map)?,
arguments: IfBlock::try_parse(config, ("session.data.pipe", id, "arguments"), token_map)?,
timeout: IfBlock::try_parse(config, ("session.data.pipe", id, "timeout"), token_map)
.unwrap_or_else(|| {
IfBlock::new::<()>(format!("session.data.pipe.{id}.timeout"), [], "30s")
}),
})
}
fn parse_milter(config: &mut Config, id: &str, token_map: &TokenMap) -> Option<Milter> {
let hostname = config
.value_require(("session.data.milter", id, "hostname"))?
.to_string();
let port = config.property_require(("session.data.milter", id, "port"))?;
Some(Milter {
enable: IfBlock::try_parse(config, ("session.data.milter", id, "enable"), token_map)
.unwrap_or_else(|| {
IfBlock::new::<()>(format!("session.data.milter.{id}.enable"), [], "false")
}),
addrs: format!("{}:{}", hostname, port)
.to_socket_addrs()
.map_err(|err| {
config.new_build_error(
("session.data.milter", id, "hostname"),
format!("Unable to resolve milter hostname {hostname}: {err}"),
)
})
.ok()?
.collect(),
hostname,
port,
timeout_connect: config
.property_or_default(("session.data.milter", id, "timeout.connect"), "30s")
.unwrap_or_else(|| Duration::from_secs(30)),
timeout_command: config
.property_or_default(("session.data.milter", id, "timeout.command"), "30s")
.unwrap_or_else(|| Duration::from_secs(30)),
timeout_data: config
.property_or_default(("session.data.milter", id, "timeout.data"), "60s")
.unwrap_or_else(|| Duration::from_secs(60)),
tls: config
.property_or_default(("session.data.milter", id, "tls"), "false")
.unwrap_or_default(),
tls_allow_invalid_certs: config
.property_or_default(("session.data.milter", id, "allow-invalid-certs"), "false")
.unwrap_or_default(),
tempfail_on_error: config
.property_or_default(
("session.data.milter", id, "options.tempfail-on-error"),
"true",
)
.unwrap_or(true),
max_frame_len: config
.property_or_default(
("session.data.milter", id, "options.max-response-size"),
"52428800",
)
.unwrap_or(52428800),
protocol_version: match config
.property_or_default::<u32>(("session.data.milter", id, "options.version"), "6")
.unwrap_or(6)
{
6 => MilterVersion::V6,
2 => MilterVersion::V2,
v => {
config.new_parse_error(
("session.data.milter", id, "options.version"),
format!("Unsupported milter protocol version {v}"),
);
MilterVersion::V6
}
},
flags_actions: config.property(("session.data.milter", id, "options.flags.actions")),
flags_protocol: config.property(("session.data.milter", id, "options.flags.protocol")),
})
}
impl Default for SessionConfig {
fn default() -> Self {
Self {
timeout: IfBlock::new::<()>("session.timeout", [], "5m"),
duration: IfBlock::new::<()>("session.duration", [], "10m"),
transfer_limit: IfBlock::new::<()>("session.transfer-limit", [], "262144000"),
throttle: SessionThrottle {
connect: Default::default(),
mail_from: Default::default(),
rcpt_to: Default::default(),
},
connect: Connect {
hostname: IfBlock::new::<()>(
"server.connect.hostname",
[],
"key_get('default', 'hostname')",
),
script: IfBlock::empty("session.connect.script"),
greeting: IfBlock::new::<()>(
"session.connect.greeting",
[],
"key_get('default', 'hostname') + ' Stalwart ESMTP at your service'",
),
},
ehlo: Ehlo {
script: IfBlock::empty("session.ehlo.script"),
require: IfBlock::new::<()>("session.ehlo.require", [], "true"),
reject_non_fqdn: IfBlock::new::<()>(
"session.ehlo.reject-non-fqdn",
[("local_port == 25", "true")],
"false",
),
},
auth: Auth {
directory: IfBlock::new::<()>(
"session.auth.directory",
#[cfg(feature = "test_mode")]
[],
#[cfg(not(feature = "test_mode"))]
[("local_port != 25", "'*'")],
"false",
),
mechanisms: IfBlock::new::<Mechanism>(
"session.auth.mechanisms",
[("local_port != 25 && is_tls", "[plain, login]")],
"false",
),
require: IfBlock::new::<()>(
"session.auth.require",
#[cfg(feature = "test_mode")]
[],
#[cfg(not(feature = "test_mode"))]
[("local_port != 25", "true")],
"false",
),
must_match_sender: IfBlock::new::<()>("session.auth.must-match-sender", [], "true"),
errors_max: IfBlock::new::<()>("session.auth.errors.total", [], "3"),
errors_wait: IfBlock::new::<()>("session.auth.errors.wait", [], "5s"),
},
mail: Mail {
script: IfBlock::empty("session.mail.script"),
rewrite: IfBlock::empty("session.mail.rewrite"),
},
rcpt: Rcpt {
script: IfBlock::empty("session.rcpt."),
relay: IfBlock::new::<()>(
"session.rcpt.relay",
[("!is_empty(authenticated_as)", "true")],
"false",
),
directory: IfBlock::new::<()>(
"session.rcpt.directory",
[],
#[cfg(feature = "test_mode")]
"false",
#[cfg(not(feature = "test_mode"))]
"'*'",
),
rewrite: IfBlock::empty("session.rcpt.rewrite"),
errors_max: IfBlock::new::<()>("session.rcpt.errors.total", [], "5"),
errors_wait: IfBlock::new::<()>("session.rcpt.errors.wait", [], "5s"),
max_recipients: IfBlock::new::<()>("session.rcpt.max-recipients", [], "100"),
catch_all: AddressMapping::Enable,
subaddressing: AddressMapping::Enable,
},
data: Data {
#[cfg(feature = "test_mode")]
script: IfBlock::empty("session.data.script"),
#[cfg(not(feature = "test_mode"))]
script: IfBlock::new::<()>(
"session.data.script",
[("is_empty(authenticated_as)", "'spam-filter'")],
"'track-replies'",
),
pipe_commands: Default::default(),
milters: Default::default(),
max_messages: IfBlock::new::<()>("session.data.limits.messages", [], "10"),
max_message_size: IfBlock::new::<()>("session.data.limits.size", [], "104857600"),
max_received_headers: IfBlock::new::<()>(
"session.data.limits.received-headers",
[],
"50",
),
add_received: IfBlock::new::<()>(
"session.data.add-headers.received",
[("local_port == 25", "true")],
"false",
),
add_received_spf: IfBlock::new::<()>(
"session.data.add-headers.received-spf",
[("local_port == 25", "true")],
"false",
),
add_return_path: IfBlock::new::<()>(
"session.data.add-headers.return-path",
[("local_port == 25", "true")],
"false",
),
add_auth_results: IfBlock::new::<()>(
"session.data.add-headers.auth-results",
[("local_port == 25", "true")],
"false",
),
add_message_id: IfBlock::new::<()>(
"session.data.add-headers.message-id",
[("local_port == 25", "true")],
"false",
),
add_date: IfBlock::new::<()>(
"session.data.add-headers.date",
[("local_port == 25", "true")],
"false",
),
},
extensions: Extensions {
pipelining: IfBlock::new::<()>("session.extensions.pipelining", [], "true"),
chunking: IfBlock::new::<()>("session.extensions.chunking", [], "true"),
requiretls: IfBlock::new::<()>("session.extensions.requiretls", [], "true"),
dsn: IfBlock::new::<()>(
"session.extensions.dsn",
[("!is_empty(authenticated_as)", "true")],
"false",
),
vrfy: IfBlock::new::<()>(
"session.extensions.vrfy",
[("!is_empty(authenticated_as)", "true")],
"false",
),
expn: IfBlock::new::<()>(
"session.extensions.expn",
[("!is_empty(authenticated_as)", "true")],
"false",
),
no_soliciting: IfBlock::new::<()>("session.extensions.no-soliciting", [], "''"),
future_release: IfBlock::new::<()>(
"session.extensions.future-release",
[("!is_empty(authenticated_as)", "7d")],
"false",
),
deliver_by: IfBlock::new::<()>(
"session.extensions.deliver-by",
[("!is_empty(authenticated_as)", "15d")],
"false",
),
mt_priority: IfBlock::new::<MtPriority>(
"session.extensions.mt-priority",
[("!is_empty(authenticated_as)", "mixer")],
"false",
),
},
mta_sts_policy: None,
}
}
}
#[derive(Default)]
pub struct Mechanism(u64);
impl ParseValue for Mechanism {
fn parse_value(value: &str) -> utils::config::Result<Self> {
Ok(Mechanism(match value.to_ascii_uppercase().as_str() {
"LOGIN" => AUTH_LOGIN,
"PLAIN" => AUTH_PLAIN,
"XOAUTH2" => AUTH_XOAUTH2,
"OAUTHBEARER" => AUTH_OAUTHBEARER,
/*"SCRAM-SHA-256-PLUS" => AUTH_SCRAM_SHA_256_PLUS,
"SCRAM-SHA-256" => AUTH_SCRAM_SHA_256,
"SCRAM-SHA-1-PLUS" => AUTH_SCRAM_SHA_1_PLUS,
"SCRAM-SHA-1" => AUTH_SCRAM_SHA_1,
"XOAUTH" => AUTH_XOAUTH,
"9798-M-DSA-SHA1" => AUTH_9798_M_DSA_SHA1,
"9798-M-ECDSA-SHA1" => AUTH_9798_M_ECDSA_SHA1,
"9798-M-RSA-SHA1-ENC" => AUTH_9798_M_RSA_SHA1_ENC,
"9798-U-DSA-SHA1" => AUTH_9798_U_DSA_SHA1,
"9798-U-ECDSA-SHA1" => AUTH_9798_U_ECDSA_SHA1,
"9798-U-RSA-SHA1-ENC" => AUTH_9798_U_RSA_SHA1_ENC,
"EAP-AES128" => AUTH_EAP_AES128,
"EAP-AES128-PLUS" => AUTH_EAP_AES128_PLUS,
"ECDH-X25519-CHALLENGE" => AUTH_ECDH_X25519_CHALLENGE,
"ECDSA-NIST256P-CHALLENGE" => AUTH_ECDSA_NIST256P_CHALLENGE,
"EXTERNAL" => AUTH_EXTERNAL,
"GS2-KRB5" => AUTH_GS2_KRB5,
"GS2-KRB5-PLUS" => AUTH_GS2_KRB5_PLUS,
"GSS-SPNEGO" => AUTH_GSS_SPNEGO,
"GSSAPI" => AUTH_GSSAPI,
"KERBEROS_V4" => AUTH_KERBEROS_V4,
"KERBEROS_V5" => AUTH_KERBEROS_V5,
"NMAS-SAMBA-AUTH" => AUTH_NMAS_SAMBA_AUTH,
"NMAS_AUTHEN" => AUTH_NMAS_AUTHEN,
"NMAS_LOGIN" => AUTH_NMAS_LOGIN,
"NTLM" => AUTH_NTLM,
"OAUTH10A" => AUTH_OAUTH10A,
"OPENID20" => AUTH_OPENID20,
"OTP" => AUTH_OTP,
"SAML20" => AUTH_SAML20,
"SECURID" => AUTH_SECURID,
"SKEY" => AUTH_SKEY,
"SPNEGO" => AUTH_SPNEGO,
"SPNEGO-PLUS" => AUTH_SPNEGO_PLUS,
"SXOVER-PLUS" => AUTH_SXOVER_PLUS,
"CRAM-MD5" => AUTH_CRAM_MD5,
"DIGEST-MD5" => AUTH_DIGEST_MD5,
"ANONYMOUS" => AUTH_ANONYMOUS,*/
_ => return Err(format!("Unsupported mechanism {:?}.", value)),
}))
}
}
impl<'x> TryFrom<Variable<'x>> for Mechanism {
type Error = ();
fn try_from(value: Variable<'x>) -> Result<Self, Self::Error> {
match value {
Variable::Integer(value) => Ok(Mechanism(value as u64)),
Variable::Array(items) => {
let mut mechanism = 0;
for item in items {
match item {
Variable::Integer(value) => mechanism |= value as u64,
_ => return Err(()),
}
}
Ok(Mechanism(mechanism))
}
_ => Err(()),
}
}
}
impl From<Mechanism> for Constant {
fn from(value: Mechanism) -> Self {
Constant::Integer(value.0 as i64)
}
}
impl ConstantValue for Mechanism {
fn add_constants(token_map: &mut crate::expr::tokenizer::TokenMap) {
token_map
.add_constant("login", Mechanism(AUTH_LOGIN))
.add_constant("plain", Mechanism(AUTH_PLAIN))
.add_constant("xoauth2", Mechanism(AUTH_XOAUTH2))
.add_constant("oauthbearer", Mechanism(AUTH_OAUTHBEARER));
}
}
impl From<Mechanism> for u64 {
fn from(value: Mechanism) -> Self {
value.0
}
}
impl From<u64> for Mechanism {
fn from(value: u64) -> Self {
Mechanism(value)
}
}
impl<'x> TryFrom<Variable<'x>> for MtPriority {
type Error = ();
fn try_from(value: Variable<'x>) -> Result<Self, Self::Error> {
match value {
Variable::Integer(value) => match value {
2 => Ok(MtPriority::Mixer),
3 => Ok(MtPriority::Stanag4406),
4 => Ok(MtPriority::Nsep),
_ => Err(()),
},
Variable::String(value) => MtPriority::parse_value(&value).map_err(|_| ()),
_ => Err(()),
}
}
}
impl From<MtPriority> for Constant {
fn from(value: MtPriority) -> Self {
Constant::Integer(match value {
MtPriority::Mixer => 2,
MtPriority::Stanag4406 => 3,
MtPriority::Nsep => 4,
})
}
}
impl ConstantValue for MtPriority {
fn add_constants(token_map: &mut TokenMap) {
token_map
.add_constant("mixer", MtPriority::Mixer)
.add_constant("stanag4406", MtPriority::Stanag4406)
.add_constant("nsep", MtPriority::Nsep);
}
}