| # Copyright 2014-2015, Tresys Technology, LLC |
| # |
| # This file is part of SETools. |
| # |
| # SETools is free software: you can redistribute it and/or modify |
| # it under the terms of the GNU Lesser General Public License as |
| # published by the Free Software Foundation, either version 2.1 of |
| # the License, or (at your option) any later version. |
| # |
| # SETools is distributed in the hope that it will be useful, |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| # GNU Lesser General Public License for more details. |
| # |
| # You should have received a copy of the GNU Lesser General Public |
| # License along with SETools. If not, see |
| # <http://www.gnu.org/licenses/>. |
| # |
| from itertools import product |
| from collections import namedtuple |
| |
| from . import exception |
| from . import qpol |
| from . import symbol |
| |
| truth_table_row = namedtuple("truth_table_row", ["values", "result"]) |
| |
| |
| def boolean_factory(policy, name): |
| """Factory function for creating Boolean statement objects.""" |
| |
| if isinstance(name, Boolean): |
| assert name.policy == policy |
| return name |
| elif isinstance(name, qpol.qpol_bool_t): |
| return Boolean(policy, name) |
| |
| try: |
| return Boolean(policy, qpol.qpol_bool_t(policy, str(name))) |
| except ValueError: |
| raise exception.InvalidBoolean("{0} is not a valid Boolean".format(name)) |
| |
| |
| def condexpr_factory(policy, name): |
| """Factory function for creating conditional expression objects.""" |
| |
| if not isinstance(name, qpol.qpol_cond_t): |
| raise TypeError("Conditional expressions cannot be looked up.") |
| |
| return ConditionalExpr(policy, name) |
| |
| |
| class Boolean(symbol.PolicySymbol): |
| |
| """A Boolean.""" |
| |
| @property |
| def state(self): |
| """The default state of the Boolean.""" |
| return bool(self.qpol_symbol.state(self.policy)) |
| |
| def statement(self): |
| """The policy statement.""" |
| return "bool {0} {1};".format(self, str(self.state).lower()) |
| |
| |
| class ConditionalExpr(symbol.PolicySymbol): |
| |
| """A conditional policy expression.""" |
| |
| _cond_expr_val_to_text = { |
| qpol.QPOL_COND_EXPR_NOT: "!", |
| qpol.QPOL_COND_EXPR_OR: "||", |
| qpol.QPOL_COND_EXPR_AND: "&&", |
| qpol.QPOL_COND_EXPR_XOR: "^", |
| qpol.QPOL_COND_EXPR_EQ: "==", |
| qpol.QPOL_COND_EXPR_NEQ: "!="} |
| |
| _cond_expr_val_to_precedence = { |
| qpol.QPOL_COND_EXPR_NOT: 5, |
| qpol.QPOL_COND_EXPR_OR: 1, |
| qpol.QPOL_COND_EXPR_AND: 3, |
| qpol.QPOL_COND_EXPR_XOR: 2, |
| qpol.QPOL_COND_EXPR_EQ: 4, |
| qpol.QPOL_COND_EXPR_NEQ: 4} |
| |
| def __contains__(self, other): |
| for expr_node in self.qpol_symbol.expr_node_iter(self.policy): |
| expr_node_type = expr_node.expr_type(self.policy) |
| |
| if expr_node_type == qpol.QPOL_COND_EXPR_BOOL and other == \ |
| boolean_factory(self.policy, expr_node.get_boolean(self.policy)): |
| return True |
| |
| return False |
| |
| def __str__(self): |
| # qpol representation is in postfix notation. This code |
| # converts it to infix notation. Parentheses are added |
| # to ensure correct expressions, though they may end up |
| # being overused. Set previous operator at start to the |
| # highest precedence (NOT) so if there is a single binary |
| # operator, no parentheses are output |
| stack = [] |
| prev_op_precedence = self._cond_expr_val_to_precedence[qpol.QPOL_COND_EXPR_NOT] |
| for expr_node in self.qpol_symbol.expr_node_iter(self.policy): |
| expr_node_type = expr_node.expr_type(self.policy) |
| |
| if expr_node_type == qpol.QPOL_COND_EXPR_BOOL: |
| # append the boolean name |
| nodebool = boolean_factory( |
| self.policy, expr_node.get_boolean(self.policy)) |
| stack.append(str(nodebool)) |
| elif expr_node_type == qpol.QPOL_COND_EXPR_NOT: # unary operator |
| operand = stack.pop() |
| operator = self._cond_expr_val_to_text[expr_node_type] |
| op_precedence = self._cond_expr_val_to_precedence[expr_node_type] |
| |
| # NOT is the highest precedence, so only need |
| # parentheses if the operand is a subexpression |
| if isinstance(operand, list): |
| subexpr = [operator, "(", operand, ")"] |
| else: |
| subexpr = [operator, operand] |
| |
| stack.append(subexpr) |
| prev_op_precedence = op_precedence |
| else: |
| operand1 = stack.pop() |
| operand2 = stack.pop() |
| operator = self._cond_expr_val_to_text[expr_node_type] |
| op_precedence = self._cond_expr_val_to_precedence[expr_node_type] |
| |
| if prev_op_precedence > op_precedence: |
| # if previous operator is of higher precedence |
| # no parentheses are needed. |
| subexpr = [operand1, operator, operand2] |
| else: |
| subexpr = ["(", operand1, operator, operand2, ")"] |
| |
| stack.append(subexpr) |
| prev_op_precedence = op_precedence |
| |
| return self.__unwind_subexpression(stack) |
| |
| def __unwind_subexpression(self, expr): |
| ret = [] |
| |
| # do a string.join on sublists (subexpressions) |
| for i in expr: |
| if isinstance(i, list): |
| ret.append(self.__unwind_subexpression(i)) |
| else: |
| ret.append(i) |
| |
| return ' '.join(ret) |
| |
| @property |
| def booleans(self): |
| """The set of Booleans in the expression.""" |
| bools = set() |
| |
| for expr_node in self.qpol_symbol.expr_node_iter(self.policy): |
| expr_node_type = expr_node.expr_type(self.policy) |
| |
| if expr_node_type == qpol.QPOL_COND_EXPR_BOOL: |
| bools.add(boolean_factory(self.policy, expr_node.get_boolean(self.policy))) |
| |
| return bools |
| |
| def evaluate(self, **kwargs): |
| """ |
| Evaluate the expression with the stated boolean values. |
| |
| Keyword Parameters: |
| Each keyword parameter name corresponds to a boolean name |
| in the expression |
| |
| Return: bool |
| """ |
| bools = sorted(self.booleans) |
| |
| if sorted(kwargs.keys()) != bools: |
| raise ValueError("Boolean values not set correctly.") |
| |
| stack = [] |
| for expr_node in self.qpol_symbol.expr_node_iter(self.policy): |
| expr_node_type = expr_node.expr_type(self.policy) |
| |
| if expr_node_type == qpol.QPOL_COND_EXPR_BOOL: |
| nodebool = boolean_factory(self.policy, expr_node.get_boolean(self.policy)) |
| stack.append(kwargs[nodebool]) |
| elif expr_node_type == qpol.QPOL_COND_EXPR_NOT: |
| operand = stack.pop() |
| operator = self._cond_expr_val_to_text[expr_node_type] |
| stack.append(not operand) |
| else: |
| operand1 = stack.pop() |
| operand2 = stack.pop() |
| operator = self._cond_expr_val_to_text[expr_node_type] |
| if operator == "||": |
| stack.append(operand1 or operand2) |
| elif operator == "&&": |
| stack.append(operand1 and operand2) |
| elif operator == "^": |
| stack.append(operand1 ^ operand2) |
| elif operator == "==": |
| stack.append(operand1 == operand2) |
| else: # not equal |
| stack.append(operand1 != operand2) |
| |
| return stack[0] |
| |
| def truth_table(self): |
| """ |
| Generate a truth table for this expression. |
| |
| Return: list |
| |
| List item: |
| tuple: values, result |
| |
| Tuple item: |
| values: Dictionary keyed on Boolean names |
| with each value being T/F. |
| result: Evaluation result for the expression |
| given the values. |
| """ |
| bools = sorted(str(b) for b in self.booleans) |
| |
| truth_table = [] |
| |
| # create a list of all combinations of T/F for each Boolean |
| truth_list = list(product([True, False], repeat=len(bools))) |
| |
| for row in truth_list: |
| values = {bools[i]: row[i] for i in range(len(bools))} |
| truth_table.append(truth_table_row(values, self.evaluate(**values))) |
| |
| return truth_table |
| |
| def statement(self): |
| raise exception.NoStatement |