1#![forbid(unsafe_code)]
23
24use std::collections::{BTreeMap, HashMap, HashSet};
25use std::path::Path;
26
27use serde::Deserialize;
28
29pub use trit_bdd::{BddLimits, VarOrder};
30
31use trit_bdd::{
32 BddError, BddRequest, order_variables, premise_counterexample_bdd_with, satisfiable_bdd_with,
33};
34use trit_core::algebra::Algebra;
35use trit_core::ast::Expr;
36use trit_core::limits::{LimitError, Limits, check_expr};
37use trit_core::parser::Parser;
38use trit_core::value::Ternary;
39use trit_eval::Assignment;
40use trit_sat::{SymbolicError, premise_counterexample, satisfiable};
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum Engine {
45 Sat,
47 Bdd,
49}
50
51impl Engine {
52 pub fn parse(name: &str) -> Option<Self> {
54 match name.to_ascii_lowercase().as_str() {
55 "sat" => Some(Self::Sat),
56 "bdd" => Some(Self::Bdd),
57 _ => None,
58 }
59 }
60
61 pub const fn as_str(self) -> &'static str {
63 match self {
64 Self::Sat => "sat",
65 Self::Bdd => "bdd",
66 }
67 }
68}
69
70#[derive(Debug, Clone, Deserialize)]
72#[serde(deny_unknown_fields)]
73pub struct Policy {
74 #[serde(default = "default_algebra")]
76 pub algebra: String,
77 #[serde(default)]
79 pub binary: Vec<String>,
80 #[serde(default)]
82 pub assumptions: Vec<String>,
83 #[serde(default)]
85 pub definitions: BTreeMap<String, String>,
86 pub assertions: Vec<Assertion>,
88}
89
90#[derive(Debug, Clone, Deserialize)]
92#[serde(deny_unknown_fields)]
93pub struct Assertion {
94 pub name: String,
96 pub expr: String,
98}
99
100fn default_algebra() -> String {
101 "kleene".to_string()
102}
103
104#[derive(Debug, Clone, PartialEq, Eq)]
106pub struct AssertionResult {
107 pub name: String,
109 pub holds: bool,
111 pub counterexample: Option<Assignment>,
113 pub nodes: Option<usize>,
115}
116
117#[derive(Debug, thiserror::Error)]
119pub enum PolicyError {
120 #[error("读取策略失败: {0}")]
122 Io(String),
123 #[error("策略 JSON 无效: {0}")]
125 Json(String),
126 #[error("未知语义: {0}(可选 kleene / lukasiewicz / bochvar)")]
128 Algebra(String),
129 #[error("{what} 解析失败: {error}")]
131 Parse {
132 what: String,
134 error: trit_core::parser::ParseError,
136 },
137 #[error("定义循环: {0}")]
139 Cycle(String),
140 #[error("前提不一致:没有合法赋值能让所有 assumptions 为 True")]
142 InconsistentAssumptions,
143 #[error("策略没有断言:拒绝作为门禁运行")]
145 NoAssertions,
146 #[error("{kind} 名称 {name:?} 不合法:{reason}")]
148 InvalidName {
149 kind: &'static str,
151 name: String,
153 reason: String,
155 },
156 #[error("binary 里的 {0:?} 是派生定义;binary 只允许原始变量")]
158 BinaryIsDefinition(String),
159 #[error("binary 列表重复:{0}")]
161 DuplicateBinary(String),
162 #[error("定义展开超过节点预算 {limit}")]
164 ExpandBudget {
165 limit: usize,
167 },
168 #[error("定义引用深度超过 {limit}")]
170 DefinitionDepth {
171 limit: usize,
173 },
174 #[error("SAT 错误: {0}")]
176 Sat(#[from] SymbolicError),
177 #[error("BDD 错误: {0}")]
179 Bdd(#[from] BddError),
180 #[error("表达式超出预算: {0}")]
182 Limit(#[from] LimitError),
183}
184
185impl From<serde_json::Error> for PolicyError {
186 fn from(value: serde_json::Error) -> Self {
187 Self::Json(value.to_string())
188 }
189}
190
191impl Policy {
192 pub fn load(path: impl AsRef<Path>) -> Result<Self, PolicyError> {
194 let text = std::fs::read_to_string(path.as_ref())
195 .map_err(|e| PolicyError::Io(format!("{}: {e}", path.as_ref().display())))?;
196 Self::from_json(&text)
197 }
198
199 pub fn from_json(text: &str) -> Result<Self, PolicyError> {
201 Ok(serde_json::from_str(text)?)
202 }
203
204 pub fn verify(&self, engine: Engine) -> Result<Vec<AssertionResult>, PolicyError> {
206 self.verify_with_bdd_config(engine, None, BddLimits::default())
207 }
208
209 pub fn verify_with_bdd_order(
211 &self,
212 engine: Engine,
213 bdd_order: Option<VarOrder>,
214 ) -> Result<Vec<AssertionResult>, PolicyError> {
215 self.verify_with_bdd_config(engine, bdd_order, BddLimits::default())
216 }
217
218 pub fn verify_with_bdd_config(
220 &self,
221 engine: Engine,
222 bdd_order: Option<VarOrder>,
223 bdd_limits: BddLimits,
224 ) -> Result<Vec<AssertionResult>, PolicyError> {
225 self.verify_with_bdd_config_and_jobs(engine, bdd_order, bdd_limits, 1)
226 }
227
228 pub fn verify_with_bdd_config_and_jobs(
233 &self,
234 engine: Engine,
235 bdd_order: Option<VarOrder>,
236 bdd_limits: BddLimits,
237 jobs: usize,
238 ) -> Result<Vec<AssertionResult>, PolicyError> {
239 if self.assertions.is_empty() {
240 return Err(PolicyError::NoAssertions);
241 }
242 let alg = Algebra::parse(&self.algebra)
243 .ok_or_else(|| PolicyError::Algebra(self.algebra.clone()))?;
244 let binary = validate_binary(&self.binary, &self.definitions)?;
245 let mut definitions = BTreeMap::new();
246 for (name, src) in &self.definitions {
247 Expr::try_var(name.clone()).map_err(|e| PolicyError::InvalidName {
248 kind: "definition",
249 name: name.clone(),
250 reason: e.to_string(),
251 })?;
252 definitions.insert(name.clone(), parse_expr(name, src)?);
253 }
254 let limits = Limits {
255 max_vars: usize::MAX,
256 ..Limits::default()
257 };
258 validate_definition_graph(&definitions, limits)?;
260 let truth = Expr::Const(Ternary::True);
261 let mut assumptions = Vec::with_capacity(self.assumptions.len());
262 for (i, src) in self.assumptions.iter().enumerate() {
263 let raw = parse_expr(&format!("assumption[{i}]"), src)?;
264 assumptions.push(expand_with_limits(&raw, &definitions, limits)?);
265 }
266 let premise = assumptions.into_iter().fold(truth.clone(), Expr::and);
267 check_expr(&premise, limits)?;
268 let satisfiable_premise = match engine {
269 Engine::Sat => satisfiable(&premise, alg, &binary)?,
270 Engine::Bdd => {
271 let vars = bdd_order.map(|order| order_variables(&premise, &premise, order));
272 let request = BddRequest {
273 order: vars.as_deref(),
274 limits: bdd_limits,
275 };
276 satisfiable_bdd_with(&premise, alg, &binary, &request)?
277 }
278 };
279 if satisfiable_premise.is_none() {
280 return Err(PolicyError::InconsistentAssumptions);
281 }
282
283 let context = VerifyContext {
284 engine,
285 bdd_order,
286 bdd_limits,
287 definitions: &definitions,
288 premise: &premise,
289 alg,
290 binary: &binary,
291 limits,
292 };
293 trit_core::parallel::map(&self.assertions, jobs, |assertion| context.check(assertion))
294 }
295}
296
297struct VerifyContext<'a> {
298 engine: Engine,
299 bdd_order: Option<VarOrder>,
300 bdd_limits: BddLimits,
301 definitions: &'a BTreeMap<String, Expr>,
302 premise: &'a Expr,
303 alg: Algebra,
304 binary: &'a HashSet<String>,
305 limits: Limits,
306}
307
308impl VerifyContext<'_> {
309 fn check(&self, assertion: &Assertion) -> Result<AssertionResult, PolicyError> {
310 let raw = parse_expr(&assertion.name, &assertion.expr)?;
311 let expr = expand_with_limits(&raw, self.definitions, self.limits)?;
312 check_expr(&expr, self.limits)?;
313 match self.engine {
314 Engine::Sat => {
315 let witness = premise_counterexample(self.premise, &expr, self.alg, self.binary)?;
316 Ok(AssertionResult {
317 name: assertion.name.clone(),
318 holds: witness.is_none(),
319 counterexample: witness,
320 nodes: None,
321 })
322 }
323 Engine::Bdd => {
324 let vars = self
325 .bdd_order
326 .map(|order| order_variables(self.premise, &expr, order));
327 let request = BddRequest {
328 order: vars.as_deref(),
329 limits: self.bdd_limits,
330 };
331 let check = premise_counterexample_bdd_with(
332 self.premise,
333 &expr,
334 self.alg,
335 self.binary,
336 &request,
337 )?;
338 Ok(AssertionResult {
339 name: assertion.name.clone(),
340 holds: check.counterexample.is_none(),
341 counterexample: check.counterexample,
342 nodes: Some(check.nodes),
343 })
344 }
345 }
346 }
347}
348
349fn parse_expr(what: &str, src: &str) -> Result<Expr, PolicyError> {
350 Parser::parse(src).map_err(|error| PolicyError::Parse {
351 what: what.to_string(),
352 error,
353 })
354}
355
356fn validate_binary(
358 list: &[String],
359 definitions: &BTreeMap<String, String>,
360) -> Result<HashSet<String>, PolicyError> {
361 let mut set = HashSet::new();
362 for name in list {
363 let reason = match Parser::parse(name) {
364 Ok(Expr::Var(parsed)) if parsed == *name => None,
365 Ok(Expr::Const(_)) => Some("是保留常量 token".to_string()),
366 Ok(_) => Some("不是单个标识符".to_string()),
367 Err(e) => Some(e.to_string()),
368 };
369 if let Some(reason) = reason {
370 return Err(PolicyError::InvalidName {
371 kind: "binary",
372 name: name.clone(),
373 reason,
374 });
375 }
376 if definitions.contains_key(name) {
377 return Err(PolicyError::BinaryIsDefinition(name.clone()));
378 }
379 if !set.insert(name.clone()) {
380 return Err(PolicyError::DuplicateBinary(name.clone()));
381 }
382 }
383 Ok(set)
384}
385
386fn definition_deps<'a>(definitions: &'a BTreeMap<String, Expr>, expr: &Expr) -> Vec<&'a str> {
388 expr.variables()
389 .into_iter()
390 .filter_map(|name| {
391 definitions
392 .get_key_value(&name)
393 .map(|(key, _)| key.as_str())
394 })
395 .collect()
396}
397
398fn validate_definition_graph(
400 definitions: &BTreeMap<String, Expr>,
401 limits: Limits,
402) -> Result<(), PolicyError> {
403 #[derive(Clone, Copy, PartialEq, Eq)]
404 enum State {
405 New,
406 Visiting,
407 Done,
408 }
409
410 struct Frame<'a> {
411 name: &'a str,
412 children: Vec<&'a str>,
413 next: usize,
414 }
415
416 let mut state: HashMap<&str, State> = definitions
417 .keys()
418 .map(|name| (name.as_str(), State::New))
419 .collect();
420 for root in definitions.keys() {
421 let root = root.as_str();
422 if state.get(root).copied() != Some(State::New) {
423 continue;
424 }
425 state.insert(root, State::Visiting);
426 let mut stack = vec![Frame {
427 name: root,
428 children: definition_deps(definitions, &definitions[root]),
429 next: 0,
430 }];
431 while !stack.is_empty() {
432 let top = stack.len() - 1;
433 if stack[top].next < stack[top].children.len() {
434 let child = stack[top].children[stack[top].next];
435 stack[top].next += 1;
436 match state.get(child).copied().unwrap_or(State::New) {
437 State::New => {
438 if stack.len() + 1 > limits.max_depth {
439 return Err(PolicyError::DefinitionDepth {
440 limit: limits.max_depth,
441 });
442 }
443 state.insert(child, State::Visiting);
444 stack.push(Frame {
445 name: child,
446 children: definition_deps(definitions, &definitions[child]),
447 next: 0,
448 });
449 }
450 State::Visiting => {
451 let mut cycle: Vec<&str> = stack.iter().map(|frame| frame.name).collect();
452 cycle.push(child);
453 return Err(PolicyError::Cycle(cycle.join(" -> ")));
454 }
455 State::Done => {}
456 }
457 } else {
458 let name = stack[top].name;
459 stack.pop();
460 state.insert(name, State::Done);
461 }
462 }
463 }
464 Ok(())
465}
466
467struct Expander<'a> {
469 definitions: &'a BTreeMap<String, Expr>,
470 stack: Vec<String>,
471 nodes_left: usize,
472 node_limit: usize,
473 max_depth: usize,
474}
475
476impl<'a> Expander<'a> {
477 fn new(definitions: &'a BTreeMap<String, Expr>, limits: Limits) -> Self {
478 Self {
479 definitions,
480 stack: Vec::new(),
481 nodes_left: limits.max_nodes,
482 node_limit: limits.max_nodes,
483 max_depth: limits.max_depth,
484 }
485 }
486
487 fn take_node(&mut self) -> Result<(), PolicyError> {
488 if self.nodes_left == 0 {
489 return Err(PolicyError::ExpandBudget {
490 limit: self.node_limit,
491 });
492 }
493 self.nodes_left -= 1;
494 Ok(())
495 }
496
497 fn check_depth(&self, depth: usize) -> Result<(), PolicyError> {
498 if depth > self.max_depth {
499 return Err(PolicyError::Limit(LimitError::Depth {
500 limit: self.max_depth,
501 }));
502 }
503 Ok(())
504 }
505
506 fn join(
507 &mut self,
508 left: (Expr, usize),
509 right: (Expr, usize),
510 f: impl FnOnce(Expr, Expr) -> Expr,
511 ) -> Result<(Expr, usize), PolicyError> {
512 let depth = 1 + left.1.max(right.1);
513 self.check_depth(depth)?;
514 self.take_node()?;
515 Ok((f(left.0, right.0), depth))
516 }
517
518 fn expand(&mut self, expr: &Expr) -> Result<(Expr, usize), PolicyError> {
519 match expr {
520 Expr::Const(_) => {
521 self.take_node()?;
522 Ok((expr.clone(), 1))
523 }
524 Expr::Var(name) => {
525 let Some(definition) = self.definitions.get(name) else {
526 self.take_node()?;
527 return Ok((expr.clone(), 1));
528 };
529 if self.stack.len() >= self.max_depth {
530 return Err(PolicyError::DefinitionDepth {
531 limit: self.max_depth,
532 });
533 }
534 if self.stack.iter().any(|n| n == name) {
535 let mut cycle = self.stack.clone();
536 cycle.push(name.clone());
537 return Err(PolicyError::Cycle(cycle.join(" -> ")));
538 }
539 self.stack.push(name.clone());
540 let expanded = self.expand(definition);
541 self.stack.pop();
542 expanded
543 }
544 Expr::Not(inner) => {
545 let (inner, depth) = self.expand(inner)?;
546 let depth = 1 + depth;
547 self.check_depth(depth)?;
548 self.take_node()?;
549 Ok((Expr::logical_not(inner), depth))
550 }
551 Expr::And(a, b) => {
552 let a = self.expand(a)?;
553 let b = self.expand(b)?;
554 self.join(a, b, Expr::and)
555 }
556 Expr::Or(a, b) => {
557 let a = self.expand(a)?;
558 let b = self.expand(b)?;
559 self.join(a, b, Expr::or)
560 }
561 Expr::Xor(a, b) => {
562 let a = self.expand(a)?;
563 let b = self.expand(b)?;
564 self.join(a, b, Expr::xor)
565 }
566 Expr::Implies(a, b) => {
567 let a = self.expand(a)?;
568 let b = self.expand(b)?;
569 self.join(a, b, Expr::implies)
570 }
571 Expr::Equiv(a, b) => {
572 let a = self.expand(a)?;
573 let b = self.expand(b)?;
574 self.join(a, b, Expr::equiv)
575 }
576 }
577 }
578}
579
580fn expand_with_limits(
581 expr: &Expr,
582 definitions: &BTreeMap<String, Expr>,
583 limits: Limits,
584) -> Result<Expr, PolicyError> {
585 let mut expander = Expander::new(definitions, limits);
586 Ok(expander.expand(expr)?.0)
587}
588
589#[cfg(test)]
590mod tests {
591 use super::*;
592
593 fn defs(pairs: &[(&str, &str)]) -> BTreeMap<String, Expr> {
594 pairs
595 .iter()
596 .map(|(name, src)| (name.to_string(), Parser::parse(src).unwrap()))
597 .collect()
598 }
599
600 #[test]
601 fn expand_budget_is_exact_and_fails_before_growth() {
602 let definitions = defs(&[("d0", "a"), ("d1", "d0 & d0")]);
603 let expr = Parser::parse("d1").unwrap();
604 let exact = Limits {
605 max_nodes: 3,
606 ..Limits::default()
607 };
608 assert!(expand_with_limits(&expr, &definitions, exact).is_ok());
609 let tight = Limits {
610 max_nodes: 2,
611 ..Limits::default()
612 };
613 assert!(matches!(
614 expand_with_limits(&expr, &definitions, tight),
615 Err(PolicyError::ExpandBudget { limit: 2 })
616 ));
617 }
618
619 #[test]
620 fn expand_depth_is_enforced() {
621 let definitions = defs(&[("d0", "a"), ("d1", "d0"), ("d2", "d1"), ("d3", "d2")]);
622 let expr = Parser::parse("d3").unwrap();
623 let limits = Limits {
624 max_depth: 3,
625 ..Limits::default()
626 };
627 assert!(matches!(
628 expand_with_limits(&expr, &definitions, limits),
629 Err(PolicyError::DefinitionDepth { limit: 3 })
630 ));
631 }
632}
633
634#[test]
635fn parallel_verify_matches_serial() {
636 let binary = (0..64).map(|index| format!("v{index}")).collect();
637 let assertions = (0..64)
638 .map(|index| Assertion {
639 name: format!("tautology{index}"),
640 expr: format!("v{index} | !v{index}"),
641 })
642 .collect();
643 let policy = Policy {
644 algebra: "kleene".to_string(),
645 binary,
646 assumptions: Vec::new(),
647 definitions: BTreeMap::new(),
648 assertions,
649 };
650 let serial = policy
651 .verify_with_bdd_config_and_jobs(Engine::Sat, None, BddLimits::default(), 1)
652 .unwrap();
653 let parallel = policy
654 .verify_with_bdd_config_and_jobs(Engine::Sat, None, BddLimits::default(), 4)
655 .unwrap();
656 assert_eq!(serial, parallel);
657 assert!(serial.iter().all(|result| result.holds));
658}