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());
}
#[test]
fn unknown_token_errors() {
let e = parse_scanflags("SYN,QXYZ").unwrap_err();
let s = e.to_string();
assert!(
s.contains("unknown") || s.contains("scanflags"),
"unexpected error: {s}"
);
}
#[test]
fn parses_rst_syn_ece_cwr() {
let f = parse_scanflags("RST,SYN,ECE,CWR").unwrap();
assert_eq!(
f,
TcpFlags::RST | TcpFlags::SYN | TcpFlags::ECE | TcpFlags::CWR
);
}
#[test]
fn parses_comma_separated_mixed_case() {
let f = parse_scanflags("syn,fin").unwrap();
assert_eq!(f, TcpFlags::SYN | TcpFlags::FIN);
}
#[test]
fn parses_fin_only() {
assert_eq!(parse_scanflags("FIN").unwrap(), TcpFlags::FIN);
}
#[test]
fn parses_all_common_flags() {
let f = parse_scanflags("SYN,ACK,FIN,RST,PSH,URG,ECE,CWR").unwrap();
assert_eq!(
f,
TcpFlags::SYN
| TcpFlags::ACK
| TcpFlags::FIN
| TcpFlags::RST
| TcpFlags::PSH
| TcpFlags::URG
| TcpFlags::ECE
| TcpFlags::CWR
);
}
#[test]
fn parses_whitespace_and_comma_mix() {
let f = parse_scanflags(" SYN , ACK ").unwrap();
assert_eq!(f, TcpFlags::SYN | TcpFlags::ACK);
}
#[test]
fn parses_rst_syn_scan_combo() {
assert_eq!(
parse_scanflags("RSTSYN").unwrap(),
TcpFlags::RST | TcpFlags::SYN
);
}
#[test]
fn duplicate_flag_tokens_or_bits() {
let f = parse_scanflags("SYN SYN").unwrap();
assert_eq!(f, TcpFlags::SYN);
}
#[test]
fn null_scan_flags_empty_is_error() {
assert!(parse_scanflags("NULL").is_err());
}
#[test]
fn parses_psh_only() {
assert_eq!(parse_scanflags("PSH").unwrap(), TcpFlags::PSH);
}
#[test]
fn parses_urg_fin_combo() {
assert_eq!(
parse_scanflags("URG FIN").unwrap(),
TcpFlags::URG | TcpFlags::FIN
);
}
#[test]
fn parses_lowercase_glued_synack() {
assert_eq!(
parse_scanflags("synack").unwrap(),
TcpFlags::SYN | TcpFlags::ACK
);
}
#[test]
fn parses_ece_only() {
assert_eq!(parse_scanflags("ECE").unwrap(), TcpFlags::ECE);
}
#[test]
fn parses_cwr_only() {
assert_eq!(parse_scanflags("CWR").unwrap(), TcpFlags::CWR);
}
#[test]
fn parses_syn_fin_rst_combo() {
assert_eq!(
parse_scanflags("SYN,FIN,RST").unwrap(),
TcpFlags::SYN | TcpFlags::FIN | TcpFlags::RST
);
}
#[test]
fn parses_mixed_case_fin_ack() {
assert_eq!(
parse_scanflags("Fin,ack").unwrap(),
TcpFlags::FIN | TcpFlags::ACK
);
}
#[test]
fn parses_finack_glued() {
assert_eq!(
parse_scanflags("FINACK").unwrap(),
TcpFlags::FIN | TcpFlags::ACK
);
}
#[test]
fn parses_pshurgrst_glued() {
assert_eq!(
parse_scanflags("PSHURGRST").unwrap(),
TcpFlags::PSH | TcpFlags::URG | TcpFlags::RST
);
}
#[test]
fn parses_pipe_separated_syn_fin() {
assert_eq!(
parse_scanflags("SYN|FIN").unwrap(),
TcpFlags::SYN | TcpFlags::FIN
);
}
#[test]
fn parses_ack_rst_combo() {
assert_eq!(
parse_scanflags("ACK RST").unwrap(),
TcpFlags::ACK | TcpFlags::RST
);
}
#[test]
fn parses_tab_separated_flags() {
assert_eq!(
parse_scanflags("SYN\tACK").unwrap(),
TcpFlags::SYN | TcpFlags::ACK
);
}
#[test]
fn null_keyword_errors() {
assert!(parse_scanflags("NULL").is_err());
}
#[test]
fn xmas_style_fin_psh_urg() {
assert_eq!(
parse_scanflags("FIN,PSH,URG").unwrap(),
TcpFlags::FIN | TcpFlags::PSH | TcpFlags::URG
);
}
}