1use super::BackendError;
9
10pub const REGEX_ACCELERATOR_EVIDENCE_SCHEMA_VERSION: u32 = 1;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub enum RegexAcceleratorClass {
16 RxpLike,
18 Dpu,
20 Fpga,
22 Software,
24}
25
26impl RegexAcceleratorClass {
27 #[must_use]
29 pub const fn as_str(self) -> &'static str {
30 match self {
31 Self::RxpLike => "rxp_like",
32 Self::Dpu => "dpu",
33 Self::Fpga => "fpga",
34 Self::Software => "software",
35 }
36 }
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41pub enum RegexAcceleratorStreamMode {
42 Unavailable,
44 Block,
46 Streaming,
48 StatefulStreaming,
50}
51
52impl RegexAcceleratorStreamMode {
53 #[must_use]
55 pub const fn as_str(self) -> &'static str {
56 match self {
57 Self::Unavailable => "unavailable",
58 Self::Block => "block",
59 Self::Streaming => "streaming",
60 Self::StatefulStreaming => "stateful_streaming",
61 }
62 }
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
67pub enum RegexAcceleratorMatchSchema {
68 Unavailable,
70 Offsets,
72 PatternIdOffsets,
74 StreamPatternIdOffsets,
76}
77
78impl RegexAcceleratorMatchSchema {
79 #[must_use]
81 pub const fn as_str(self) -> &'static str {
82 match self {
83 Self::Unavailable => "unavailable",
84 Self::Offsets => "offsets",
85 Self::PatternIdOffsets => "pattern_id_offsets",
86 Self::StreamPatternIdOffsets => "stream_pattern_id_offsets",
87 }
88 }
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub struct RegexAcceleratorCapability {
94 pub backend: &'static str,
96 pub accelerator_class: RegexAcceleratorClass,
98 pub supported: bool,
100 pub device_signature: &'static str,
102 pub rule_capacity: u32,
104 pub stream_mode: RegexAcceleratorStreamMode,
106 pub match_schema: RegexAcceleratorMatchSchema,
108 pub unsupported_reason: &'static str,
110}
111
112impl RegexAcceleratorCapability {
113 #[must_use]
115 pub const fn unsupported(
116 backend: &'static str,
117 accelerator_class: RegexAcceleratorClass,
118 unsupported_reason: &'static str,
119 ) -> Self {
120 Self {
121 backend,
122 accelerator_class,
123 supported: false,
124 device_signature: "",
125 rule_capacity: 0,
126 stream_mode: RegexAcceleratorStreamMode::Unavailable,
127 match_schema: RegexAcceleratorMatchSchema::Unavailable,
128 unsupported_reason,
129 }
130 }
131
132 #[must_use]
134 pub const fn supported(
135 backend: &'static str,
136 accelerator_class: RegexAcceleratorClass,
137 device_signature: &'static str,
138 rule_capacity: u32,
139 stream_mode: RegexAcceleratorStreamMode,
140 match_schema: RegexAcceleratorMatchSchema,
141 ) -> Self {
142 Self {
143 backend,
144 accelerator_class,
145 supported: true,
146 device_signature,
147 rule_capacity,
148 stream_mode,
149 match_schema,
150 unsupported_reason: "",
151 }
152 }
153
154 pub fn require_supported(self) -> Result<Self, BackendError> {
161 if self.supported {
162 return Ok(self);
163 }
164 Err(BackendError::UnsupportedFeature {
165 name: format!("regex_accelerator:{}", self.accelerator_class.as_str()),
166 backend: self.backend.to_string(),
167 })
168 }
169
170 #[must_use]
172 pub const fn evidence(self, transfer_bytes: u64) -> RegexAcceleratorEvidence {
173 RegexAcceleratorEvidence {
174 schema_version: REGEX_ACCELERATOR_EVIDENCE_SCHEMA_VERSION,
175 backend: self.backend,
176 accelerator_class: self.accelerator_class,
177 supported: self.supported,
178 device_signature: self.device_signature,
179 rule_capacity: self.rule_capacity,
180 stream_mode: self.stream_mode,
181 match_schema: self.match_schema,
182 unsupported_reason: self.unsupported_reason,
183 transfer_bytes,
184 match_parity_required: true,
185 }
186 }
187}
188
189#[derive(Debug, Clone, Copy, PartialEq, Eq)]
191pub struct RegexAcceleratorEvidence {
192 pub schema_version: u32,
194 pub backend: &'static str,
196 pub accelerator_class: RegexAcceleratorClass,
198 pub supported: bool,
200 pub device_signature: &'static str,
202 pub rule_capacity: u32,
204 pub stream_mode: RegexAcceleratorStreamMode,
206 pub match_schema: RegexAcceleratorMatchSchema,
208 pub unsupported_reason: &'static str,
210 pub transfer_bytes: u64,
212 pub match_parity_required: bool,
214}
215
216impl RegexAcceleratorEvidence {
217 #[must_use]
219 pub fn is_complete(self) -> bool {
220 if self.schema_version != REGEX_ACCELERATOR_EVIDENCE_SCHEMA_VERSION {
221 return false;
222 }
223 if !self.match_parity_required || self.backend.is_empty() {
224 return false;
225 }
226 if self.supported {
227 !self.device_signature.is_empty()
228 && self.rule_capacity != 0
229 && self.stream_mode != RegexAcceleratorStreamMode::Unavailable
230 && self.match_schema != RegexAcceleratorMatchSchema::Unavailable
231 && self.unsupported_reason.is_empty()
232 } else {
233 self.device_signature.is_empty()
234 && self.rule_capacity == 0
235 && self.stream_mode == RegexAcceleratorStreamMode::Unavailable
236 && self.match_schema == RegexAcceleratorMatchSchema::Unavailable
237 && !self.unsupported_reason.is_empty()
238 }
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245
246 #[test]
247 fn unsupported_regex_accelerator_fails_closed_with_evidence() {
248 let capability = RegexAcceleratorCapability::unsupported(
249 "wgpu",
250 RegexAcceleratorClass::RxpLike,
251 "backend has no RXP-like regex accelerator",
252 );
253
254 let error = capability
255 .require_supported()
256 .expect_err("Fix: unsupported regex accelerator capability must fail closed");
257 match error {
258 BackendError::UnsupportedFeature { name, backend } => {
259 assert_eq!(name, "regex_accelerator:rxp_like");
260 assert_eq!(backend, "wgpu");
261 }
262 other => panic!("expected UnsupportedFeature, got {other:?}"),
263 }
264 let evidence = capability.evidence(0);
265 assert!(!evidence.supported);
266 assert_eq!(evidence.unsupported_reason, "backend has no RXP-like regex accelerator");
267 assert_eq!(evidence.stream_mode, RegexAcceleratorStreamMode::Unavailable);
268 assert_eq!(evidence.match_schema, RegexAcceleratorMatchSchema::Unavailable);
269 assert!(evidence.match_parity_required);
270 assert!(evidence.is_complete());
271 }
272
273 #[test]
274 fn supported_regex_accelerator_reports_device_signature_and_schema() {
275 let capability = RegexAcceleratorCapability::supported(
276 "rxp",
277 RegexAcceleratorClass::RxpLike,
278 "bluefield-rxp:v1",
279 4096,
280 RegexAcceleratorStreamMode::StatefulStreaming,
281 RegexAcceleratorMatchSchema::StreamPatternIdOffsets,
282 );
283
284 let supported = capability
285 .require_supported()
286 .expect("Fix: supported regex accelerator capability should pass");
287 let evidence = supported.evidence(8192);
288
289 assert!(evidence.supported);
290 assert_eq!(evidence.device_signature, "bluefield-rxp:v1");
291 assert_eq!(evidence.rule_capacity, 4096);
292 assert_eq!(
293 evidence.stream_mode,
294 RegexAcceleratorStreamMode::StatefulStreaming
295 );
296 assert_eq!(
297 evidence.match_schema,
298 RegexAcceleratorMatchSchema::StreamPatternIdOffsets
299 );
300 assert_eq!(evidence.transfer_bytes, 8192);
301 assert!(evidence.match_parity_required);
302 assert!(evidence.is_complete());
303 }
304}