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<ws>' from option strings
  parse_key_location   parse '<nbytes> <offset>' into VariableKeyLocation

All four items are pub so any transform can import them. Unit tests
are included in the module.

Issue: 8671
pull/15699/head
Jeff Lucovsky 2 months ago committed by Victor Julien
parent 72e9314402
commit 385bd4416b

@ -28,4 +28,5 @@ pub mod http_headers;
pub mod strip_whitespace;
pub mod subslice;
pub mod urldecode;
mod varkey;
pub mod xor;

@ -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::<u8>());
let parse_offset = map_res(digit1, |s: &str| s.parse::<u16>());
let (rest, (nbytes, offset)) =
separated_pair(parse_nbytes, multispace1, parse_offset).parse(input)?;
Ok((rest, VariableKeyLocation { nbytes, offset }))
}
/// Parse `<nbytes> <offset>` 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<VariableKeyLocation> {
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][..]));
}
}
Loading…
Cancel
Save