From 385bd4416b85bb31d54d293cc6dfb44e86eb017d Mon Sep 17 00:00:00 2001 From: Jeff Lucovsky Date: Mon, 18 May 2026 10:39:24 -0400 Subject: [PATCH] detect/transforms: add varkey module for variable-key helpers Introduce rust/src/detect/transforms/varkey.rs to centralise helpers used by transforms that read key material directly from the inspection buffer at transform time. Exported items: VariableKeyLocation { offset: u16, nbytes: u8 } variable_key_bytes bounds-checked slice into an inspection buffer strip_keyword_prefix strip 'keyword' from option strings parse_key_location parse ' ' into VariableKeyLocation All four items are pub so any transform can import them. Unit tests are included in the module. Issue: 8671 --- rust/src/detect/transforms/mod.rs | 1 + rust/src/detect/transforms/varkey.rs | 187 +++++++++++++++++++++++++++ 2 files changed, 188 insertions(+) create mode 100644 rust/src/detect/transforms/varkey.rs diff --git a/rust/src/detect/transforms/mod.rs b/rust/src/detect/transforms/mod.rs index b20246d484..9c33790c80 100644 --- a/rust/src/detect/transforms/mod.rs +++ b/rust/src/detect/transforms/mod.rs @@ -28,4 +28,5 @@ pub mod http_headers; pub mod strip_whitespace; pub mod subslice; pub mod urldecode; +mod varkey; pub mod xor; diff --git a/rust/src/detect/transforms/varkey.rs b/rust/src/detect/transforms/varkey.rs new file mode 100644 index 0000000000..cab9b7c379 --- /dev/null +++ b/rust/src/detect/transforms/varkey.rs @@ -0,0 +1,187 @@ +/* Copyright (C) 2026 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! Shared helpers for transforms that read key bytes from a fixed +//! position within the inspection buffer at transform time. + +use nom8::{ + character::complete::{digit1, multispace1}, + combinator::map_res, + sequence::separated_pair, + IResult, Parser, +}; + +/// Location in an inspection buffer from which key bytes are read at +/// transform time. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(super) struct VariableKeyLocation { + /// Absolute byte offset in the inspection buffer. + pub(super) offset: u16, + /// Number of bytes to read starting at `offset`. + pub(super) nbytes: u8, +} + +/// Return the key bytes at `loc` within `input`, or `None` if the +/// location is out of bounds. +pub(super) fn variable_key_bytes<'a>( + input: &'a [u8], loc: &VariableKeyLocation, +) -> Option<&'a [u8]> { + let start = loc.offset as usize; + let end = start + loc.nbytes as usize; + if end > input.len() { + return None; + } + Some(&input[start..end]) +} + +/// Strip a leading keyword from `s` if it is followed by whitespace. +/// Returns the trimmed remainder, or `None` if the keyword is absent or +/// not followed by whitespace. +pub(super) fn strip_keyword_prefix<'a>(s: &'a str, keyword: &str) -> Option<&'a str> { + s.strip_prefix(keyword) + .filter(|r| r.starts_with(|c: char| c.is_ascii_whitespace())) + .map(|r| r.trim_start()) +} + +fn parse_location(input: &str) -> IResult<&str, VariableKeyLocation> { + let parse_nbytes = map_res(digit1, |s: &str| s.parse::()); + let parse_offset = map_res(digit1, |s: &str| s.parse::()); + let (rest, (nbytes, offset)) = + separated_pair(parse_nbytes, multispace1, parse_offset).parse(input)?; + Ok((rest, VariableKeyLocation { nbytes, offset })) +} + +/// Parse ` ` from a trimmed string (the keyword has +/// already been stripped by the caller). Returns a `VariableKeyLocation` +/// or `None` if the format is wrong, a value overflows its type, or +/// `nbytes` is zero. +pub(super) fn parse_key_location(s: &str) -> Option { + match parse_location(s) { + Ok(("", loc)) if loc.nbytes > 0 => Some(loc), + _ => None, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_strip_keyword_prefix_match() { + assert_eq!(strip_keyword_prefix("var 1 0", "var"), Some("1 0")); + } + + #[test] + fn test_strip_keyword_prefix_offset() { + assert_eq!( + strip_keyword_prefix("offset 4,key", "offset"), + Some("4,key") + ); + } + + #[test] + fn test_strip_keyword_prefix_no_trailing_whitespace() { + assert_eq!(strip_keyword_prefix("var1 0", "var"), None); + } + + #[test] + fn test_strip_keyword_prefix_absent() { + assert_eq!(strip_keyword_prefix("aabb", "var"), None); + } + + #[test] + fn test_parse_key_location_basic() { + assert_eq!( + parse_key_location("1 0"), + Some(VariableKeyLocation { + nbytes: 1, + offset: 0 + }) + ); + } + + #[test] + fn test_parse_key_location_large_offset() { + assert_eq!( + parse_key_location("4 1024"), + Some(VariableKeyLocation { + nbytes: 4, + offset: 1024 + }) + ); + } + + #[test] + fn test_parse_key_location_missing_offset() { + assert_eq!(parse_key_location("1"), None); + } + + #[test] + fn test_parse_key_location_zero_nbytes() { + assert_eq!(parse_key_location("0 0"), None); + } + + #[test] + fn test_parse_key_location_nbytes_overflow() { + assert_eq!(parse_key_location("256 0"), None); + } + + #[test] + fn test_parse_key_location_offset_overflow() { + assert_eq!(parse_key_location("1 65536"), None); + } + + #[test] + fn test_variable_key_bytes_in_bounds() { + let input = b"\x42hello"; + let loc = VariableKeyLocation { + offset: 0, + nbytes: 1, + }; + assert_eq!(variable_key_bytes(input, &loc), Some(&[0x42u8][..])); + } + + #[test] + fn test_variable_key_bytes_offset_out_of_bounds() { + let input = b"\x42hello"; + let loc = VariableKeyLocation { + offset: 100, + nbytes: 1, + }; + assert_eq!(variable_key_bytes(input, &loc), None); + } + + #[test] + fn test_variable_key_bytes_end_out_of_bounds() { + let input = b"\x42hello"; + let loc = VariableKeyLocation { + offset: 5, + nbytes: 3, + }; + assert_eq!(variable_key_bytes(input, &loc), None); + } + + #[test] + fn test_variable_key_bytes_multi() { + let input = b"\x42\x37data"; + let loc = VariableKeyLocation { + offset: 0, + nbytes: 2, + }; + assert_eq!(variable_key_bytes(input, &loc), Some(&[0x42u8, 0x37][..])); + } +}