1use super::extensible_bitmap::ExtensibleBitmap;
6use super::security_context::{Level, SecurityContext, SecurityLevel};
7use super::symbols::{
8 ConstraintExpr, ConstraintTerm, CONSTRAINT_EXPR_OPERAND_TYPE_H1_H2,
9 CONSTRAINT_EXPR_OPERAND_TYPE_H1_L2, CONSTRAINT_EXPR_OPERAND_TYPE_L1_H1,
10 CONSTRAINT_EXPR_OPERAND_TYPE_L1_H2, CONSTRAINT_EXPR_OPERAND_TYPE_L1_L2,
11 CONSTRAINT_EXPR_OPERAND_TYPE_L2_H2, CONSTRAINT_EXPR_OPERAND_TYPE_ROLE,
12 CONSTRAINT_EXPR_OPERAND_TYPE_TYPE, CONSTRAINT_EXPR_OPERAND_TYPE_USER,
13 CONSTRAINT_EXPR_OPERATOR_TYPE_DOM, CONSTRAINT_EXPR_OPERATOR_TYPE_DOMBY,
14 CONSTRAINT_EXPR_OPERATOR_TYPE_EQ, CONSTRAINT_EXPR_OPERATOR_TYPE_INCOMP,
15 CONSTRAINT_EXPR_OPERATOR_TYPE_NE, CONSTRAINT_EXPR_WITH_NAMES_OPERAND_TYPE_TARGET_MASK,
16 CONSTRAINT_TERM_TYPE_AND_OPERATOR, CONSTRAINT_TERM_TYPE_EXPR,
17 CONSTRAINT_TERM_TYPE_EXPR_WITH_NAMES, CONSTRAINT_TERM_TYPE_NOT_OPERATOR,
18 CONSTRAINT_TERM_TYPE_OR_OPERATOR,
19};
20use super::{ParseStrategy, RoleId, TypeId, UserId};
21
22use std::cmp::Ordering;
23use std::collections::HashSet;
24use std::num::NonZeroU32;
25use thiserror::Error;
26
27#[derive(Clone, Debug, Error, Eq, PartialEq)]
28pub(super) enum ConstraintError {
29 #[error("missing names for constraint term")]
30 MissingNames,
31 #[error("invalid constraint term type {type_:?}")]
32 InvalidTermType { type_: u32 },
33 #[error("invalid operator type for context expression: {type_:?}")]
34 InvalidContextOperatorType { type_: u32 },
35 #[error("invalid operand type for context expression: {type_:?}")]
36 InvalidContextOperandType { type_: u32 },
37 #[error("invalid operand type for context expression with names: {type_:?}")]
38 InvalidContextWithNamesOperandType { type_: u32 },
39 #[error("invalid operator type {operator:?} for operands ({left:?}, {right:?})")]
40 InvalidContextOperatorForOperands {
41 operator: ContextOperator,
42 left: ContextOperand,
43 right: ContextOperand,
44 },
45 #[error("invalid pair of context operands: ({left:?}, {right:?})")]
46 InvalidContextOperands { left: ContextOperand, right: ContextOperand },
47 #[error("invalid constraint term sequence")]
48 InvalidTermSequence,
49}
50
51pub(super) fn evaluate_constraint<PS: ParseStrategy>(
63 constraint_expr: &ConstraintExpr<PS>,
64 source: &SecurityContext,
65 target: &SecurityContext,
66) -> Result<bool, ConstraintError> {
67 let nodes = constraint_expr
68 .constraint_terms()
69 .iter()
70 .map(|term| ConstraintNode::try_from_constraint_term(term, source, target))
71 .collect::<Result<Vec<_>, _>>()?;
72 let mut stack = Vec::new();
73 for node in nodes.iter() {
74 match node {
75 ConstraintNode::Leaf(expr) => stack.push(expr.evaluate()?),
76 ConstraintNode::Branch(op) => match op {
77 BooleanOperator::Not => {
78 let arg = stack.last_mut().ok_or(ConstraintError::InvalidTermSequence)?;
79 *arg = !*arg;
80 }
81 BooleanOperator::And => {
82 let right = stack.pop().ok_or(ConstraintError::InvalidTermSequence)?;
83 let left = stack.last_mut().ok_or(ConstraintError::InvalidTermSequence)?;
84 *left = *left && right;
85 }
86 BooleanOperator::Or => {
87 let right = stack.pop().ok_or(ConstraintError::InvalidTermSequence)?;
88 let left = stack.last_mut().ok_or(ConstraintError::InvalidTermSequence)?;
89 *left = *left || right;
90 }
91 },
92 }
93 }
94 let result = stack.pop().ok_or(ConstraintError::InvalidTermSequence)?;
95 if !stack.is_empty() {
96 return Err(ConstraintError::InvalidTermSequence);
97 }
98 Ok(result)
99}
100
101#[derive(Debug, Eq, PartialEq)]
103enum ConstraintNode {
104 Branch(BooleanOperator),
105 Leaf(ContextExpression),
106}
107
108impl ConstraintNode {
109 fn try_from_constraint_term<PS: ParseStrategy>(
110 value: &ConstraintTerm<PS>,
111 source: &SecurityContext,
112 target: &SecurityContext,
113 ) -> Result<ConstraintNode, ConstraintError> {
114 if let Ok(op) = BooleanOperator::try_from_constraint_term(value) {
115 Ok(ConstraintNode::Branch(op))
116 } else {
117 Ok(ConstraintNode::Leaf(ContextExpression::try_from_constraint_term(
118 value, source, target,
119 )?))
120 }
121 }
122}
123
124#[derive(Debug, Eq, PartialEq)]
128enum BooleanOperator {
129 Not,
130 And,
131 Or,
132}
133
134impl BooleanOperator {
135 fn try_from_constraint_term<PS: ParseStrategy>(
136 value: &ConstraintTerm<PS>,
137 ) -> Result<BooleanOperator, ConstraintError> {
138 match value.constraint_term_type() {
139 CONSTRAINT_TERM_TYPE_NOT_OPERATOR => Ok(BooleanOperator::Not),
140 CONSTRAINT_TERM_TYPE_AND_OPERATOR => Ok(BooleanOperator::And),
141 CONSTRAINT_TERM_TYPE_OR_OPERATOR => Ok(BooleanOperator::Or),
142 _ => Err(ConstraintError::InvalidTermType { type_: value.constraint_term_type() }),
143 }
144 }
145}
146
147#[derive(Clone, Debug, Eq, PartialEq)]
150pub(super) enum ContextOperator {
151 Equal, NotEqual, Dominates, DominatedBy, Incomparable, }
157
158#[derive(Clone, Debug, Eq, PartialEq)]
160pub(super) enum ContextOperand {
161 UserId(UserId),
162 RoleId(RoleId),
163 TypeId(TypeId),
164 Level(SecurityLevel),
165 UserIds(HashSet<UserId>),
166 RoleIds(HashSet<RoleId>),
167 TypeIds(HashSet<TypeId>),
168}
169
170#[derive(Debug, Eq, PartialEq)]
174struct ContextExpression {
175 left: ContextOperand,
176 right: ContextOperand,
177 operator: ContextOperator,
178}
179
180impl ContextExpression {
181 fn evaluate(&self) -> Result<bool, ConstraintError> {
182 match (&self.left, &self.right) {
183 (ContextOperand::UserId(_), ContextOperand::UserId(_))
184 | (ContextOperand::RoleId(_), ContextOperand::RoleId(_))
185 | (ContextOperand::TypeId(_), ContextOperand::TypeId(_)) => match self.operator {
186 ContextOperator::Equal => Ok(self.left == self.right),
187 ContextOperator::NotEqual => Ok(self.left != self.right),
188 _ => Err(ConstraintError::InvalidContextOperatorForOperands {
189 operator: self.operator.clone(),
190 left: self.left.clone(),
191 right: self.right.clone(),
192 }),
193 },
194 (ContextOperand::UserId(id), ContextOperand::UserIds(ids)) => match self.operator {
195 ContextOperator::Equal => Ok(ids.contains(id)),
196 ContextOperator::NotEqual => Ok(!ids.contains(id)),
197 _ => Err(ConstraintError::InvalidContextOperatorForOperands {
198 operator: self.operator.clone(),
199 left: self.left.clone(),
200 right: self.right.clone(),
201 }),
202 },
203 (ContextOperand::RoleId(id), ContextOperand::RoleIds(ids)) => match self.operator {
204 ContextOperator::Equal => Ok(ids.contains(id)),
205 ContextOperator::NotEqual => Ok(!ids.contains(id)),
206 _ => Err(ConstraintError::InvalidContextOperatorForOperands {
207 operator: self.operator.clone(),
208 left: self.left.clone(),
209 right: self.right.clone(),
210 }),
211 },
212 (ContextOperand::TypeId(id), ContextOperand::TypeIds(ids)) => match self.operator {
213 ContextOperator::Equal => Ok(ids.contains(id)),
214 ContextOperator::NotEqual => Ok(!ids.contains(id)),
215 _ => Err(ConstraintError::InvalidContextOperatorForOperands {
216 operator: self.operator.clone(),
217 left: self.left.clone(),
218 right: self.right.clone(),
219 }),
220 },
221 (ContextOperand::Level(left), ContextOperand::Level(right)) => match self.operator {
222 ContextOperator::Equal => Ok(left.compare(right) == Some(Ordering::Equal)),
223 ContextOperator::NotEqual => Ok(left.compare(right) != Some(Ordering::Equal)),
224 ContextOperator::Dominates => Ok(left.dominates(right)),
225 ContextOperator::DominatedBy => Ok(right.dominates(left)),
226 ContextOperator::Incomparable => Ok(left.compare(right).is_none()),
227 },
228 _ => Err(ConstraintError::InvalidContextOperands {
229 left: self.left.clone(),
230 right: self.right.clone(),
231 }),
232 }
233 }
234
235 fn try_from_constraint_term<PS: ParseStrategy>(
236 value: &ConstraintTerm<PS>,
237 source: &SecurityContext,
238 target: &SecurityContext,
239 ) -> Result<ContextExpression, ConstraintError> {
240 let (left, right) = match value.constraint_term_type() {
241 CONSTRAINT_TERM_TYPE_EXPR => {
242 ContextExpression::operands_from_expr(value.expr_operand_type(), source, target)
243 }
244 CONSTRAINT_TERM_TYPE_EXPR_WITH_NAMES => {
245 if let Some(names) = value.names() {
246 ContextExpression::operands_from_expr_with_names(
247 value.expr_operand_type(),
248 names,
249 source,
250 target,
251 )
252 } else {
253 Err(ConstraintError::MissingNames)
254 }
255 }
256 _ => Err(ConstraintError::InvalidTermType { type_: value.constraint_term_type() }),
257 }?;
258 let operator = match value.expr_operator_type() {
259 CONSTRAINT_EXPR_OPERATOR_TYPE_EQ => Ok(ContextOperator::Equal),
260 CONSTRAINT_EXPR_OPERATOR_TYPE_NE => Ok(ContextOperator::NotEqual),
261 CONSTRAINT_EXPR_OPERATOR_TYPE_DOM => Ok(ContextOperator::Dominates),
262 CONSTRAINT_EXPR_OPERATOR_TYPE_DOMBY => Ok(ContextOperator::DominatedBy),
263 CONSTRAINT_EXPR_OPERATOR_TYPE_INCOMP => Ok(ContextOperator::Incomparable),
264 _ => Err(ConstraintError::InvalidContextOperatorType {
265 type_: value.expr_operator_type(),
266 }),
267 }?;
268 Ok(ContextExpression { left, right, operator })
269 }
270
271 fn operands_from_expr(
272 operand_type: u32,
273 source: &SecurityContext,
274 target: &SecurityContext,
275 ) -> Result<(ContextOperand, ContextOperand), ConstraintError> {
276 match operand_type {
277 CONSTRAINT_EXPR_OPERAND_TYPE_USER => {
278 Ok((ContextOperand::UserId(source.user()), ContextOperand::UserId(target.user())))
279 }
280 CONSTRAINT_EXPR_OPERAND_TYPE_ROLE => {
281 Ok((ContextOperand::RoleId(source.role()), ContextOperand::RoleId(target.role())))
282 }
283 CONSTRAINT_EXPR_OPERAND_TYPE_TYPE => {
284 Ok((ContextOperand::TypeId(source.type_()), ContextOperand::TypeId(target.type_())))
285 }
286 CONSTRAINT_EXPR_OPERAND_TYPE_L1_L2 => Ok((
287 ContextOperand::Level(source.low_level().clone()),
288 ContextOperand::Level(target.low_level().clone()),
289 )),
290 CONSTRAINT_EXPR_OPERAND_TYPE_L1_H2 => Ok((
291 ContextOperand::Level(source.low_level().clone()),
292 ContextOperand::Level(target.effective_high_level().clone()),
293 )),
294 CONSTRAINT_EXPR_OPERAND_TYPE_H1_L2 => Ok((
295 ContextOperand::Level(source.effective_high_level().clone()),
296 ContextOperand::Level(target.low_level().clone()),
297 )),
298 CONSTRAINT_EXPR_OPERAND_TYPE_H1_H2 => Ok((
299 ContextOperand::Level(source.effective_high_level().clone()),
300 ContextOperand::Level(target.effective_high_level().clone()),
301 )),
302 CONSTRAINT_EXPR_OPERAND_TYPE_L1_H1 => Ok((
303 ContextOperand::Level(source.low_level().clone()),
304 ContextOperand::Level(source.effective_high_level().clone()),
305 )),
306 CONSTRAINT_EXPR_OPERAND_TYPE_L2_H2 => Ok((
307 ContextOperand::Level(target.low_level().clone()),
308 ContextOperand::Level(target.effective_high_level().clone()),
309 )),
310 _ => Err(ConstraintError::InvalidContextOperandType { type_: operand_type }),
311 }
312 }
313
314 fn operands_from_expr_with_names<PS: ParseStrategy>(
315 operand_type: u32,
316 names: &ExtensibleBitmap<PS>,
317 source: &SecurityContext,
318 target: &SecurityContext,
319 ) -> Result<(ContextOperand, ContextOperand), ConstraintError> {
320 let ids = names
321 .spans()
322 .flat_map(|span| span.low..=span.high)
323 .map(|i| NonZeroU32::new(i + 1).unwrap());
324
325 let (left, right) =
326 if operand_type & CONSTRAINT_EXPR_WITH_NAMES_OPERAND_TYPE_TARGET_MASK == 0 {
327 match operand_type {
328 CONSTRAINT_EXPR_OPERAND_TYPE_USER => Ok((
329 ContextOperand::UserId(source.user()),
330 ContextOperand::UserIds(ids.map(|id| UserId(id)).collect()),
331 )),
332 CONSTRAINT_EXPR_OPERAND_TYPE_ROLE => Ok((
333 ContextOperand::RoleId(source.role()),
334 ContextOperand::RoleIds(ids.map(|id| RoleId(id)).collect()),
335 )),
336 CONSTRAINT_EXPR_OPERAND_TYPE_TYPE => Ok((
337 ContextOperand::TypeId(source.type_()),
338 ContextOperand::TypeIds(ids.map(|id| TypeId(id)).collect()),
339 )),
340 _ => Err(ConstraintError::InvalidContextWithNamesOperandType {
341 type_: operand_type,
342 }),
343 }
344 } else {
345 match operand_type ^ CONSTRAINT_EXPR_WITH_NAMES_OPERAND_TYPE_TARGET_MASK {
346 CONSTRAINT_EXPR_OPERAND_TYPE_USER => Ok((
347 ContextOperand::UserId(target.user()),
348 ContextOperand::UserIds(ids.map(|id| UserId(id)).collect()),
349 )),
350 CONSTRAINT_EXPR_OPERAND_TYPE_ROLE => Ok((
351 ContextOperand::RoleId(target.role()),
352 ContextOperand::RoleIds(ids.map(|id| RoleId(id)).collect()),
353 )),
354 CONSTRAINT_EXPR_OPERAND_TYPE_TYPE => Ok((
355 ContextOperand::TypeId(target.type_()),
356 ContextOperand::TypeIds(ids.map(|id| TypeId(id)).collect()),
357 )),
358 _ => Err(ConstraintError::InvalidContextWithNamesOperandType {
359 type_: operand_type,
360 }),
361 }
362 }?;
363 Ok((left, right))
364 }
365}
366
367#[cfg(test)]
368mod tests {
369 use super::*;
370 use crate::policy::{find_class_by_name, parse_policy_by_reference};
371
372 fn normalize_context_expr(expr: ContextExpression) -> ContextExpression {
373 let (left, right) = match expr.operator {
374 ContextOperator::Dominates | ContextOperator::DominatedBy => (expr.left, expr.right),
375 ContextOperator::Equal | ContextOperator::NotEqual | ContextOperator::Incomparable => {
376 match (&expr.left, &expr.right) {
377 (ContextOperand::UserId(left), ContextOperand::UserId(right)) => (
378 ContextOperand::UserId(std::cmp::min(*left, *right)),
379 ContextOperand::UserId(std::cmp::max(*left, *right)),
380 ),
381 (ContextOperand::TypeId(left), ContextOperand::TypeId(right)) => (
382 ContextOperand::TypeId(std::cmp::min(*left, *right)),
383 ContextOperand::TypeId(std::cmp::max(*left, *right)),
384 ),
385 (ContextOperand::RoleId(left), ContextOperand::RoleId(right)) => (
386 ContextOperand::RoleId(std::cmp::min(*left, *right)),
387 ContextOperand::RoleId(std::cmp::max(*left, *right)),
388 ),
389 _ => (expr.left, expr.right),
390 }
391 }
392 };
393 ContextExpression { operator: expr.operator, left, right }
394 }
395
396 fn normalize(expr: Vec<ConstraintNode>) -> Vec<ConstraintNode> {
397 expr.into_iter()
398 .map(|node| match node {
399 ConstraintNode::Leaf(context_expr) => {
400 ConstraintNode::Leaf(normalize_context_expr(context_expr))
401 }
402 ConstraintNode::Branch(_) => node,
403 })
404 .collect()
405 }
406
407 #[test]
408 fn decode_constraint_expr() {
409 let policy_bytes = include_bytes!("../../testdata/micro_policies/constraints_policy.pp");
410 let policy = parse_policy_by_reference(policy_bytes.as_slice())
411 .expect("parse policy")
412 .validate()
413 .expect("validate policy");
414 let parsed_policy = policy.0.parsed_policy();
415
416 let source = policy
417 .parse_security_context(b"user0:object_r:type0:s0-s0".into())
418 .expect("valid source security context");
419 let target = policy
420 .parse_security_context(b"user1:object_r:security_t:s0:c0-s0:c0".into())
421 .expect("valid target security context");
422
423 let class = find_class_by_name(parsed_policy.classes(), "class_constraint_nested")
424 .expect("look up class");
425 let constraints = class.constraints();
426 assert_eq!(constraints.len(), 1);
427 let constraint = &constraints[0].constraint_expr();
428 let result: Result<Vec<ConstraintNode>, ConstraintError> = constraint
429 .constraint_terms()
430 .iter()
431 .map(|x| ConstraintNode::try_from_constraint_term(x, &source, &target))
432 .collect();
433 let constraint_nodes = normalize(result.expect("decode constraint terms"));
434 let expected = vec![
435 ConstraintNode::Leaf(ContextExpression {
437 left: ContextOperand::UserId(UserId(NonZeroU32::new(2).unwrap())),
438 right: ContextOperand::UserIds(HashSet::from([
439 UserId(NonZeroU32::new(1).unwrap()),
440 UserId(NonZeroU32::new(2).unwrap()),
441 ])),
442 operator: ContextOperator::Equal,
443 }),
444 ConstraintNode::Leaf(ContextExpression {
446 left: ContextOperand::RoleId(RoleId(NonZeroU32::new(1).unwrap())),
447 right: ContextOperand::RoleId(RoleId(NonZeroU32::new(1).unwrap())),
448 operator: ContextOperator::Equal,
449 }),
450 ConstraintNode::Branch(BooleanOperator::And),
452 ConstraintNode::Leaf(ContextExpression {
454 left: ContextOperand::UserId(UserId(NonZeroU32::new(1).unwrap())),
455 right: ContextOperand::UserId(UserId(NonZeroU32::new(2).unwrap())),
456 operator: ContextOperator::Equal,
457 }),
458 ConstraintNode::Leaf(ContextExpression {
460 left: ContextOperand::TypeId(TypeId(NonZeroU32::new(1).unwrap())),
461 right: ContextOperand::TypeId(TypeId(NonZeroU32::new(2).unwrap())),
462 operator: ContextOperator::Equal,
463 }),
464 ConstraintNode::Branch(BooleanOperator::Not),
466 ConstraintNode::Branch(BooleanOperator::And),
468 ConstraintNode::Branch(BooleanOperator::Or),
470 ];
471
472 assert_eq!(constraint_nodes, expected)
473 }
474
475 #[test]
476 fn evaluate_constraint_expr() {
477 let policy_bytes = include_bytes!("../../testdata/micro_policies/constraints_policy.pp");
478 let policy = parse_policy_by_reference(policy_bytes.as_slice())
479 .expect("parse policy")
480 .validate()
481 .expect("validate policy");
482 let parsed_policy = policy.0.parsed_policy();
483
484 let source = policy
485 .parse_security_context(b"user0:object_r:type0:s0-s0".into())
486 .expect("valid source security context");
487 let target = policy
488 .parse_security_context(b"user1:object_r:security_t:s0:c0-s0:c0".into())
489 .expect("valid target security context");
490
491 let class_constraint_eq =
492 find_class_by_name(parsed_policy.classes(), "class_constraint_eq")
493 .expect("look up class");
494 let class_constraint_eq_constraints = class_constraint_eq.constraints();
495 assert_eq!(class_constraint_eq_constraints.len(), 1);
496 let constraint_eq = &class_constraint_eq_constraints[0].constraint_expr();
498 assert_eq!(
499 evaluate_constraint(constraint_eq, &source, &target).expect("evaluate constraint"),
500 false
501 );
502
503 let class_constraint_with_and =
504 find_class_by_name(parsed_policy.classes(), "class_constraint_with_and")
505 .expect("look up class");
506 let class_constraint_with_and_constraints = class_constraint_with_and.constraints();
507 assert_eq!(class_constraint_with_and_constraints.len(), 1);
508 let constraint_with_and = &class_constraint_with_and_constraints[0].constraint_expr();
510 assert_eq!(
511 evaluate_constraint(constraint_with_and, &source, &target)
512 .expect("evaluate constraint"),
513 false
514 );
515
516 let class_constraint_with_not =
517 find_class_by_name(parsed_policy.classes(), "class_constraint_with_not")
518 .expect("look up class");
519 let class_constraint_with_not_constraints = class_constraint_with_not.constraints();
520 assert_eq!(class_constraint_with_not_constraints.len(), 1);
521 let constraint_with_not = &class_constraint_with_not_constraints[0].constraint_expr();
523 assert_eq!(
524 evaluate_constraint(constraint_with_not, &source, &target)
525 .expect("evaluate constraint"),
526 true
527 );
528
529 let class_constraint_with_names =
530 find_class_by_name(parsed_policy.classes(), "class_constraint_with_names")
531 .expect("look up class");
532 let class_constraint_with_names_constraints = class_constraint_with_names.constraints();
533 assert_eq!(class_constraint_with_names_constraints.len(), 1);
534 let constraint_with_names = &class_constraint_with_names_constraints[0].constraint_expr();
536 assert_eq!(
537 evaluate_constraint(constraint_with_names, &source, &target)
538 .expect("evaluate constraint"),
539 false
540 );
541
542 let class_constraint_nested =
543 find_class_by_name(parsed_policy.classes(), "class_constraint_nested")
544 .expect("look up class");
545 let class_constraint_nested_constraints = class_constraint_nested.constraints();
546 assert_eq!(class_constraint_nested_constraints.len(), 1);
547 let constraint_nested = &class_constraint_nested_constraints[0].constraint_expr();
549 assert_eq!(
550 evaluate_constraint(constraint_nested, &source, &target).expect("evaluate constraint"),
551 true
552 )
553 }
554
555 #[test]
556 fn evaluate_mls_constraint_expr() {
557 let policy_bytes = include_bytes!("../../testdata/micro_policies/constraints_policy.pp");
558 let policy = parse_policy_by_reference(policy_bytes.as_slice())
559 .expect("parse policy")
560 .validate()
561 .expect("validate policy");
562 let parsed_policy = policy.0.parsed_policy();
563
564 let source = policy
565 .parse_security_context(b"user0:object_r:type0:s0-s0".into())
566 .expect("valid source security context");
567 let target = policy
568 .parse_security_context(b"user1:object_r:security_t:s0:c0-s0:c0".into())
569 .expect("valid target security context");
570
571 let class = find_class_by_name(parsed_policy.classes(), "class_mls_constraints")
572 .expect("look up class");
573 let constraints = class.constraints();
574 let expected = vec![
576 false, false, true, false, false, false, ];
583 for (i, constraint) in constraints.iter().enumerate() {
584 assert_eq!(
585 evaluate_constraint(constraint.constraint_expr(), &source, &target)
586 .expect("evaluate constraint",),
587 expected[i],
588 "constraint {}",
589 i
590 );
591 }
592 }
593}