use anyhow::{bail, Result};
use pnet::packet::tcp::TcpFlags;
pub fn parse_scanflags(s: &str) -> Result<u8> {
let s = s.trim();
if s.is_empty() {
bail!("--scanflags must not be empty");
}
const KEYWORDS: &[(&str, u8)] = &[
("SYN", TcpFlags::SYN),
("ACK", TcpFlags::ACK),
("FIN", TcpFlags::FIN),
("RST", TcpFlags::RST),
("PSH", TcpFlags::PSH),
("URG", TcpFlags::URG),
("ECE", TcpFlags::ECE),
("CWR", TcpFlags::CWR),
];
let normalized = s.replace(['|', ','], " ");
let mut flags = 0u8;
for word in normalized.split_whitespace() {
if word.is_empty() {
continue;
}
let word_u = word.to_ascii_uppercase();
let mut sub = word_u.as_str();
while !sub.is_empty() {
let mut matched = false;
for (name, bit) in KEYWORDS.iter() {
if sub.starts_with(name) {
flags |= bit;
sub = &sub[name.len()..];
matched = true;
break;
}
}
if !matched {
bail!("unknown --scanflags token in '{word}' (unparsed suffix '{sub}')");
}
}
}
Ok(flags)
}
#[cfg(test)]
mod tests {
use super::parse_scanflags;
use pnet::packet::tcp::TcpFlags;
#[test]
fn parses_spaced_and_glued() {
let a = parse_scanflags("SYN ACK").unwrap();
let b = parse_scanflags("SYNACK").unwrap();
assert_eq!(a, b);
assert_eq!(a, TcpFlags::SYN | TcpFlags::ACK);
}
#[test]
fn parses_pipe() {
let f = parse_scanflags("URG|PSH").unwrap();
assert_eq!(f, TcpFlags::URG | TcpFlags::PSH);
}
#[test]
fn empty_errors() {
assert!(parse_scanflags("").is_err());
assert!(parse_scanflags(" ").is_err());
}
}