#![allow(clippy::expect_used)]
use crate::ops::security_detection::detector_support::{spans, DetectionError};
pub const SPEC_TOML: &str = r#"schema_version = 1
id = "security_detection.detect_base64_run"
archetype = "match-bytes-pattern"
display_name = "Detect Base64 Run"
summary = "Returns offsets of base64-shaped byte runs using a config-encoded minimum length."
category = "C"
[intrinsic]
wgsl = "security_detection_detect_base64_run"
[signature]
inputs = ["Bytes", "Bytes"]
output = "Bytes"
laws = []
equivalence_classes = ["long_run", "short_run", "mixed_alphabet", "t47_cap"]
workgroup_size = [64, 1, 1]
tags = ["security-detection", "base64", "secret-scan", "t47"]
fixtures_dir = "fixtures/"
"#;
pub const REFERENCE_VECTORS_TOML: &str = r#"[[case]]
name = "positive_secret_run"
input = "prefix QWxhZGRpbjpvcGVuIHNlc2FtZQ== suffix"
min_run_len = 16
expected_offsets = [7]
[[case]]
name = "negative_short_run"
input = "abc def ghi"
min_run_len = 8
expected_offsets = []
"#;
pub mod lowering {
#[must_use]
pub const fn source() -> &'static str {
r#"fn is_base64(b: u32) -> bool {
return (b >= 65u && b <= 90u) || (b >= 97u && b <= 122u) ||
(b >= 48u && b <= 57u) || b == 43u || b == 47u || b == 61u;
}
@compute @workgroup_size(64)
pub fn detect_base64_run(@builtin(global_invocation_id) gid: vec3<u32>) {
let i = gid.x;
if (i >= input_len || !is_base64(input[i])) { return; }
if (i > 0u && is_base64(input[i - 1u])) { return; }
var j = i;
loop {
if (j >= input_len || !is_base64(input[j])) { break; }
j = j + 1u;
}
if (j - i >= min_run_len) {
let slot = atomicAdd(&out_count, 1u);
offsets[slot] = i;
}
}"#
}
}
pub fn detect_base64_run(input: &[u8], min_run_len: u32) -> Result<Vec<u32>, DetectionError> {
spans::base64_run_offsets(input, min_run_len)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DetectBase64RunConfig {
pub min_run_len: u32,
}
pub fn detect_base64_run_config(config: &[u8]) -> Result<DetectBase64RunConfig, DetectionError> {
Ok(DetectBase64RunConfig {
min_run_len: read_u32_config(config, "min_run_len")?,
})
}
pub fn read_u32_config(config: &[u8], field: &str) -> Result<u32, DetectionError> {
let bytes: [u8; 4] = config
.get(..4)
.ok_or_else(|| format!("Fix: provide at least 4 config bytes for {field}"))?
.try_into()
.expect("slice length checked");
Ok(u32::from_le_bytes(bytes))
}
pub mod implementation {
pub use super::detect_base64_run;
pub use super::{detect_base64_run_config, read_u32_config, DetectBase64RunConfig};
pub mod kernel {
pub use super::super::{
detect_base64_run, detect_base64_run_config, read_u32_config, DetectBase64RunConfig,
};
}
pub mod lowering {
pub mod wgsl {
pub use super::super::super::lowering::source;
}
}
}