use crate::ops::security_detection::detector_support::{spans, ByteSpan, DetectionError};
pub const SPEC_TOML: &str = r#"schema_version = 1
id = "security_detection.detect_ipv6"
archetype = "match-bytes-pattern"
display_name = "Detect IPv6"
summary = "Returns offset-length spans for IPv6 addresses with hex-group validation."
category = "C"
[intrinsic]
wgsl = "security_detection_detect_ipv6"
[signature]
inputs = ["Bytes", "Bytes"]
output = "Bytes"
laws = []
equivalence_classes = ["eight_groups", "compressed", "bad_group", "t47_cap"]
workgroup_size = [64, 1, 1]
tags = ["security-detection", "ipv6", "ioc", "t47"]
fixtures_dir = "fixtures/"
"#;
pub const REFERENCE_VECTORS_TOML: &str = r#"[[case]]
name = "positive_compressed"
input = "peer 2001:db8::1 ready"
expected_spans = [{ offset = 5, len = 11 }]
[[case]]
name = "negative_bad_group"
input = "peer 2001:db8:::1 ready"
expected_spans = []
"#;
pub mod lowering {
#[must_use]
pub const fn source() -> &'static str {
r#"struct Params {
input_len: u32,
max_spans: u32,
_pad0: u32,
_pad1: u32,
}
struct SpanOutput {
count: atomic<u32>,
data: array<u32>,
}
@group(0) @binding(0) var<storage, read> input: array<u32>;
@group(0) @binding(1) var<storage, read_write> output: SpanOutput;
@group(0) @binding(2) var<uniform> params: Params;
fn is_alpha(byte: u32) -> bool {
return (byte >= 65u && byte <= 90u) || (byte >= 97u && byte <= 122u);
}
fn is_digit(byte: u32) -> bool {
return byte >= 48u && byte <= 57u;
}
fn is_hex(byte: u32) -> bool {
return is_digit(byte) || (byte >= 65u && byte <= 70u) || (byte >= 97u && byte <= 102u);
}
fn is_boundary(byte: u32) -> bool {
return !(is_alpha(byte) || is_digit(byte) || byte == 95u || byte == 45u);
}
fn parse_group(pos_in: u32) -> u32 {
var pos = pos_in;
var used = 0u;
loop {
if (pos >= params.input_len || used >= 4u || !is_hex(input[pos])) {
break;
}
pos = pos + 1u;
used = used + 1u;
}
if (used == 0u) {
return 0xffffffffu;
}
return pos;
}
fn emit_span(offset: u32, len: u32) {
let slot = atomicAdd(&output.count, 1u);
if (slot < params.max_spans) {
output.data[slot * 2u] = offset;
output.data[slot * 2u + 1u] = len;
}
}
@compute @workgroup_size(64)
fn security_detection_detect_ipv6(@builtin(global_invocation_id) gid: vec3<u32>) {
let start = gid.x;
if (start >= params.input_len || (start > 0u && !is_boundary(input[start - 1u]))) {
return;
}
var pos = start;
var groups = 0u;
var compressed = false;
loop {
if (pos + 1u < params.input_len && input[pos] == 58u && input[pos + 1u] == 58u) {
if (compressed) {
return;
}
compressed = true;
pos = pos + 2u;
continue;
}
let next = parse_group(pos);
if (next == 0xffffffffu) {
break;
}
groups = groups + 1u;
pos = next;
if (pos < params.input_len && input[pos] == 58u) {
pos = pos + 1u;
} else {
break;
}
}
let valid_shape = (compressed && groups <= 7u) || groups == 8u;
let valid_len = pos - start >= 3u;
let after_ok = pos >= params.input_len || is_boundary(input[pos]);
if (valid_shape && valid_len && after_ok) {
emit_span(start, pos - start);
}
}
"#
}
}
pub fn detect_ipv6(input: &[u8]) -> Result<Vec<ByteSpan>, DetectionError> {
spans::ipv6_spans(input)
}
pub mod implementation {
pub use super::detect_ipv6;
pub mod kernel {
pub use super::super::detect_ipv6;
}
pub mod lowering {
pub mod wgsl {
pub use super::super::super::lowering::source;
}
}
}