def test_message_field_condition(self) -> None: self.assertEqual(ETHERNET_FRAME.field_condition(INITIAL), TRUE) self.assertEqual( ETHERNET_FRAME.field_condition(Field("TPID")), Equal(Variable("Type_Length_TPID"), Number(33024, 16)), ) self.assertEqual( ETHERNET_FRAME.field_condition(Field("Type_Length")), Or( NotEqual(Variable("Type_Length_TPID"), Number(33024, 16)), Equal(Variable("Type_Length_TPID"), Number(33024, 16)), ), ) self.assertEqual( ETHERNET_FRAME.field_condition(Field("Payload")), Or( And( Or( NotEqual(Variable("Type_Length_TPID"), Number(33024, 16)), Equal(Variable("Type_Length_TPID"), Number(33024, 16)), ), LessEqual(Variable("Type_Length"), Number(1500)), ), And( Or( NotEqual(Variable("Type_Length_TPID"), Number(33024, 16)), Equal(Variable("Type_Length_TPID"), Number(33024, 16)), ), GreaterEqual(Variable("Type_Length"), Number(1536)), ), ), )
def test_expr_variables(self) -> None: self.assertEqual( Or( Greater(Variable("Y"), Number(42)), And(TRUE, Less(Variable("X"), Number(42))) ).variables(), [Variable("Y"), Variable("X")], ) self.assertEqual( Or( Greater(Variable("Y"), Number(42)), And(TRUE, Less(Variable("X"), Number(42))) ).variables(), [Variable("Y"), Variable("X")], ) self.assertEqual( Or( Greater(Variable("Y"), Number(42)), And(TRUE, Less(Variable("X"), Number(42))) ).variables(), [Variable("Y"), Variable("X")], ) self.assertEqual( Or( Greater(Variable("Y"), Number(42)), And(TRUE, Less(Variable("X"), Number(1))) ).variables(), [Variable("Y"), Variable("X")], )
def test_expr_variables_duplicates(self) -> None: self.assertEqual( And(Variable("X"), Variable("Y"), Variable("X")).variables(), [Variable("X"), Variable("Y")], ) self.assertEqual( Or(Variable("X"), Variable("Y"), Variable("X")).variables(), [Variable("X"), Variable("Y")], ) self.assertEqual( Add(Variable("X"), Variable("Y"), Variable("X")).variables(), [Variable("X"), Variable("Y")], ) self.assertEqual( Mul(Variable("X"), Variable("Y"), Variable("X")).variables(), [Variable("X"), Variable("Y")], ) self.assertEqual(Sub(Variable("X"), Variable("X")).variables(), [Variable("X")]) self.assertEqual(Div(Variable("X"), Variable("X")).variables(), [Variable("X")]) self.assertEqual( Or( Greater(Variable("X"), Number(42)), And(TRUE, Less(Variable("X"), Number(1))) ).variables(), [Variable("X")], )
def constraints(self, name: str, proof: bool = False) -> Expr: if proof: return And( And(*[Equal(Variable(l), v) for l, v in self.literals.items()]), Or(*[Equal(Variable(name), Variable(l)) for l in self.literals.keys()]), ) return TRUE
def enumeration_functions(enum: Enumeration) -> List[Subprogram]: common_precondition = And( Less(Value('Offset'), Number(8)), Equal( Length('Buffer'), Add( Div(Add(Size(enum.base_name), Value('Offset'), Number(-1)), Number(8)), Number(1)))) control_expression = LogCall( f'Convert_To_{enum.base_name} (Buffer, Offset)') validation_expression: Expr if enum.always_valid: validation_expression = Value('True') else: validation_cases: List[Tuple[Expr, Expr]] = [] validation_cases.extend( (value, Value('True')) for value in enum.literals.values()) validation_cases.append((Value('others'), Value('False'))) validation_expression = CaseExpression(control_expression, validation_cases) validation_function = ExpressionFunction( f'Valid_{enum.name}', 'Boolean', [('Buffer', 'Types.Bytes'), ('Offset', 'Natural')], validation_expression, [Precondition(common_precondition)]) function_name = f'Convert_To_{enum.name}' parameters = [('Buffer', 'Types.Bytes'), ('Offset', 'Natural')] precondition = Precondition( And(common_precondition, LogCall(f'Valid_{enum.name} (Buffer, Offset)'))) conversion_cases: List[Tuple[Expr, Expr]] = [] conversion_function: Subprogram if enum.always_valid: conversion_cases.extend((value, Aggregate(Value('True'), Value(key))) for key, value in enum.literals.items()) conversion_cases.append( (Value('others'), Aggregate(Value('False'), Value('Raw')))) conversion_function = Function( function_name, enum.name, parameters, [Declaration('Raw', enum.base_name, control_expression)], [ReturnStatement(CaseExpression(Value('Raw'), conversion_cases))], [precondition]) else: conversion_cases.extend( (value, Value(key)) for key, value in enum.literals.items()) conversion_cases.append( (Value('others'), LogCall(f'Unreachable_{enum.name}'))) conversion_function = ExpressionFunction( function_name, enum.name, parameters, CaseExpression(control_expression, conversion_cases), [precondition]) return [validation_function, conversion_function]
def valid_variants(field: Field) -> Iterator[LogExpr]: for variant_id, variant in field.variants.items(): expression: LogExpr = LogCall( f'Valid_{field.name}_{variant_id} (Buffer)') if field.condition is not TRUE: expression = And(expression, field.condition) yield expression.simplified(variant.facts).simplified( value_to_call_facts([(field.name, variant_id)] + variant.previous))
def test_and() -> None: assert_equal( And(TRUE, FALSE, TRUE).z3expr(), z3.And(z3.BoolVal(True), z3.BoolVal(False), z3.BoolVal(True)), ) assert_equal( And(TRUE, TRUE, TRUE).z3expr(), z3.And(z3.BoolVal(True), z3.BoolVal(True), z3.BoolVal(True)), ) assert_equal(And(TRUE, TRUE).z3expr(), z3.And(z3.BoolVal(True), z3.BoolVal(True)))
def field_accessor_functions(field: Field, package_name: str) -> List[Subprogram]: precondition = Precondition( And(COMMON_PRECONDITION, LogCall(f'Valid_{field.name} (Buffer)'))) functions: List[Subprogram] = [] if isinstance(field.type, Array): for attribute in ['First', 'Last']: functions.append( ExpressionFunction( f'Get_{field.name}_{attribute}', 'Types.Index_Type', [('Buffer', 'Types.Bytes')], IfExpression([( LogCall(f'Valid_{field.name}_{variant_id} (Buffer)'), LogCall( f'Get_{field.name}_{variant_id}_{attribute} (Buffer)' )) for variant_id in field.variants], 'Unreachable_Types_Index_Type'), [precondition])) body: List[Statement] = [ Assignment('First', MathCall(f'Get_{field.name}_First (Buffer)')), Assignment('Last', MathCall(f'Get_{field.name}_Last (Buffer)')) ] postcondition = Postcondition( And( Equal(Value('First'), MathCall(f'Get_{field.name}_First (Buffer)')), Equal(Value('Last'), MathCall(f'Get_{field.name}_Last (Buffer)')))) if 'Payload' not in field.type.name: predicate = f'{package_name}.{field.type.name}.Is_Contained (Buffer (First .. Last))' body.append(PragmaStatement('Assume', [predicate])) postcondition.expr = And(postcondition.expr, LogCall(predicate)) functions.append( Procedure(f'Get_{field.name}', [('Buffer', 'Types.Bytes'), ('First', 'out Types.Index_Type'), ('Last', 'out Types.Index_Type')], [], body, [precondition, postcondition])) else: functions.append( ExpressionFunction( f'Get_{field.name}', field.type.name, [('Buffer', 'Types.Bytes')], IfExpression( [(LogCall(f'Valid_{field.name}_{variant_id} (Buffer)'), MathCall(f'Get_{field.name}_{variant_id} (Buffer)')) for variant_id in field.variants], f'Unreachable_{field.type.name}'), [precondition])) return functions
def constraints(self, name: str, proof: bool = False) -> Expr: if proof: return And( GreaterEqual(Variable(name), self.first), LessEqual(Variable(name), self.last) ) c: Expr = TRUE if self.first.simplified() != self.base_first.simplified(): c = GreaterEqual(Variable(name), self.first) if self.last.simplified() != self.base_last.simplified(): c = And(c, LessEqual(Variable(name), self.last)) return c.simplified()
def test_and(self) -> None: self.assertEqual( And(TRUE, FALSE, TRUE).z3expr(), z3.And(z3.BoolVal(True), z3.BoolVal(False), z3.BoolVal(True)), ) self.assertEqual( And(TRUE, TRUE, TRUE).z3expr(), z3.And(z3.BoolVal(True), z3.BoolVal(True), z3.BoolVal(True)), ) self.assertEqual( And(TRUE, TRUE).z3expr(), z3.And(z3.BoolVal(True), z3.BoolVal(True)))
def test_ass_expr_converted(self) -> None: self.assertEqual( And(Variable("X"), Number(1)).converted( lambda x: Name(x.name) if isinstance(x, Variable) else x ), And(Name("X"), Number(1)), ) self.assertEqual( Mul(Variable("X"), Number(1)).converted( lambda x: Name("Y") if x == Mul(Variable("X"), Number(1)) else x ), Name("Y"), )
def test_expr_contains() -> None: assert Variable("X") in Or( Greater(Variable("Y"), Number(42)), And(TRUE, Less(Variable("X"), Number(42))) ) assert Variable("Z") not in Or( Greater(Variable("Y"), Number(42)), And(TRUE, Less(Variable("X"), Number(42))) ) assert Less(Variable("X"), Number(42)) in Or( Greater(Variable("Y"), Number(42)), And(TRUE, Less(Variable("X"), Number(42))) ) assert Less(Variable("Z"), Number(42)) not in Or( Greater(Variable("Y"), Number(42)), And(TRUE, Less(Variable("X"), Number(1))) )
def __init__(self, name: str, first: MathExpr, last: MathExpr, size: MathExpr) -> None: first_num = first.simplified() if not isinstance(first_num, Number): raise ModelError(f'first of "{name}" contains variable') last_num = last.simplified() if not isinstance(last_num, Number): raise ModelError(f'last of "{name}" contains variable') if first_num < Number(0): raise ModelError(f'first of "{name}" negative') if first_num > last_num: raise ModelError(f'range of "{name}" negative') size_num = size.simplified() if not isinstance(size_num, Number): raise ModelError(f'size of "{name}" contains variable') if log(int(last_num) + 1) / log(2) > int(size_num): raise ModelError(f'size for "{name}" too small') super().__init__(name) self.__first = first self.__last = last self.__size = size constraints: LogExpr = TRUE if self.first.simplified() != self.base_first.simplified(): constraints = GreaterEqual(Value(self.name), self.first) if self.last.simplified() != self.base_last.simplified(): constraints = And(constraints, LessEqual(Value(self.name), self.last)) self.__constraints = constraints.simplified()
def create_structural_valid_function() -> UnitPart: specification = FunctionSpecification( "Structural_Valid", "Boolean", [Parameter(["Ctx"], "Context"), Parameter(["Fld"], "Field")], ) return UnitPart( [SubprogramDeclaration(specification)], [ ExpressionFunctionDeclaration( specification, And( Or(*[ Equal( Selected( Indexed(Variable("Ctx.Cursors"), Variable("Fld")), "State"), Variable(s), ) for s in ("S_Valid", "S_Structural_Valid") ])), ) ], )
def create_verify_message_procedure( message: Message, context_invariant: Sequence[Expr]) -> UnitPart: specification = ProcedureSpecification( "Verify_Message", [InOutParameter(["Ctx"], "Context")]) return UnitPart( [ SubprogramDeclaration( specification, [ Postcondition( And( Equal( Call("Has_Buffer", [Variable("Ctx")]), Old(Call("Has_Buffer", [Variable("Ctx")])), ), *context_invariant, )), ], ) ], [ SubprogramBody( specification, [], [ CallStatement( "Verify", [Variable("Ctx"), Variable(f.affixed_name)]) for f in message.fields ], ) ], )
def create_composite_accessor_procedures(self, composite_fields: Sequence[Field]) -> UnitPart: def specification(field: Field) -> ProcedureSpecification: return ProcedureSpecification(f"Get_{field.name}", [Parameter(["Ctx"], "Context")]) return UnitPart( [ SubprogramDeclaration( specification(f), [ Precondition( And( VALID_CONTEXT, Call("Has_Buffer", [Name("Ctx")]), Call("Present", [Name("Ctx"), Name(f.affixed_name)]), ) ) ], [ FormalSubprogramDeclaration( ProcedureSpecification( f"Process_{f.name}", [Parameter([f.name], self.types.bytes)] ) ) ], ) for f in composite_fields ], [ SubprogramBody( specification(f), [ ObjectDeclaration( ["First"], self.types.index, Call( self.types.byte_index, [Selected(Indexed("Ctx.Cursors", Name(f.affixed_name)), "First")], ), True, ), ObjectDeclaration( ["Last"], self.types.index, Call( self.types.byte_index, [Selected(Indexed("Ctx.Cursors", Name(f.affixed_name)), "Last")], ), True, ), ], [ CallStatement( f"Process_{f.name}", [Slice("Ctx.Buffer.all", Name("First"), Name("Last"))], ) ], ) for f in composite_fields ], )
def assign(self, value: str, check: bool = True) -> None: prefixed_value = ( ID(value) if value.startswith(str(self._type.package)) or not self.__imported or self.__builtin else self._type.package * value) if Variable(prefixed_value) not in self.literals: raise KeyError(f"{value} is not a valid enum value") r = (And(*self._type.constraints( "__VALUE__", check, not self.__imported)).substituted( mapping={ **self.literals, **{ Variable("__VALUE__"): self._type.literals[prefixed_value.name] }, **{ Length("__VALUE__"): self._type.size }, }).simplified()) assert r == TRUE self._value = ( str(prefixed_value) if self.__imported and not self.__builtin else str(prefixed_value.name), self._type.literals[prefixed_value.name], )
def test_ass_expr_findall() -> None: assert_equal( And(Equal(Variable("X"), Number(1)), Variable("Y"), Number(2)).findall( lambda x: isinstance(x, Number) ), [Number(1), Number(2)], )
def variant_accessor_functions(field: Field, variant_id: str, variant: Variant) -> List[Subprogram]: first_byte, last_byte, offset = field_location(field.name, variant_id, variant) name = f'Get_{field.name}_{variant_id}' precondition = Precondition( And(COMMON_PRECONDITION, LogCall(f'Valid_{field.name}_{variant_id} (Buffer)'))) functions: List[Subprogram] = [] if isinstance(field.type, Array): functions.append( ExpressionFunction(f'{name}_First', 'Types.Index_Type', [('Buffer', 'Types.Bytes')], first_byte, [precondition])) functions.append( ExpressionFunction(f'{name}_Last', 'Types.Index_Type', [('Buffer', 'Types.Bytes')], last_byte, [precondition])) else: functions.append( ExpressionFunction( name, field.type.name, [('Buffer', 'Types.Bytes')], Convert( field.type.name if field.type.constraints == TRUE else field.type.base_name, 'Buffer', first_byte, last_byte, offset), [precondition])) return functions
def test_conditionally_unreachable_field_outgoing_multi() -> None: f2 = Field(ID("F2", Location((90, 12)))) structure = [ Link(INITIAL, Field("F1")), Link(Field("F1"), f2, LessEqual(Variable("F1"), Number(32), Location((66, 3)))), Link(Field("F1"), Field("F3"), Greater(Variable("F1"), Number(32))), Link( f2, Field("F3"), And( Greater(Variable("F1"), Number(32)), LessEqual(Variable("F1"), Number(48)), location=Location((22, 34)), ), ), Link(f2, FINAL, Greater(Variable("F1"), Number(48))), Link(Field("F3"), FINAL), ] types = { Field("F1"): MODULAR_INTEGER, Field("F2"): MODULAR_INTEGER, Field("F3"): MODULAR_INTEGER, } assert_message_model_error( structure, types, r"^" r'<stdin>:90:12: model: error: unreachable field "F2" in "P.M"\n' r"<stdin>:90:12: model: info: path 0 [(]F1 -> F2[)]:\n" r'<stdin>:66:3: model: info: unsatisfied "F1 <= 32"\n' r'<stdin>:90:12: model: info: unsatisfied "[(]F1 > 32 and F1 <= 48[)] or F1 > 48"', )
def public_context_predicate() -> Expr: return And( GreaterEqual(Call(const.TYPES_BYTE_INDEX, [Variable("First")]), Variable("Buffer_First")), LessEqual(Call(const.TYPES_BYTE_INDEX, [Variable("Last")]), Variable("Buffer_Last")), LessEqual(Variable("First"), Variable("Last")), LessEqual(Variable("Last"), Div(Last(const.TYPES_BIT_INDEX), Number(2))), )
def valid_path_to_next_field_condition( self, message: Message, field: Field, field_type: Type ) -> Sequence[Expr]: return [ If( [ ( l.condition, And( Equal( Call("Predecessor", [Name("Ctx"), Name(l.target.affixed_name)],), Name(field.affixed_name), ), Call("Valid_Next", [Name("Ctx"), Name(l.target.affixed_name)]) if l.target != FINAL else TRUE, ), ) ] ).simplified( { **{ Variable(field.name): Call("Convert", [Name("Value")]) if isinstance(field_type, Enumeration) and field_type.always_valid else Name("Value") }, **self.public_substitution(message), } ) for l in message.outgoing(field) if l.target != FINAL ]
def create_scalar_accessor_functions(scalar_fields: Mapping[Field, Scalar]) -> UnitPart: def specification(field: Field, field_type: Type) -> FunctionSpecification: return FunctionSpecification( f"Get_{field.name}", field_type.full_name, [Parameter(["Ctx"], "Context")] ) def result(field: Field, field_type: Type) -> Expr: value = Selected( Indexed("Ctx.Cursors", Name(field.affixed_name)), f"Value.{field.name}_Value" ) if isinstance(field_type, Enumeration): return Call("Convert", [value]) return value return UnitPart( [ SubprogramDeclaration( specification(f, t), [ Precondition( And(VALID_CONTEXT, Call("Valid", [Name("Ctx"), Name(f.affixed_name)])) ) ], ) for f, t in scalar_fields.items() ], [ ExpressionFunctionDeclaration(specification(f, t), result(f, t)) for f, t in scalar_fields.items() ], )
def contains_function(name: str, pdu: str, field: str, sdu: str, condition: LogExpr) -> Subprogram: success_statements: List[Statement] = [ReturnStatement(TRUE)] aspects: List[Aspect] = [ Precondition( And(LogCall(f'{pdu}.Is_Contained (Buffer)'), LogCall(f'{pdu}.Is_Valid (Buffer)'))) ] if sdu != 'null': success_statements.insert( 0, PragmaStatement('Assume', [( f'{sdu}.Is_Contained (Buffer ({pdu}.Get_{field}_First (Buffer)' f' .. {pdu}.Get_{field}_Last (Buffer)))')])) aspects.append( Postcondition( IfExpression([ (LogCall(f'{name}\'Result'), LogCall((f'{sdu}.Is_Contained (Buffer (' f'{pdu}.Get_{field}_First (Buffer)' f' .. {pdu}.Get_{field}_Last (Buffer)))'))) ]))) return Function(name, 'Boolean', [('Buffer', 'Types.Bytes')], [], [ IfStatement([(condition, success_statements)]), ReturnStatement(FALSE) ], aspects)
def test_message_incoming(self) -> None: self.assertEqual(ETHERNET_FRAME.incoming(INITIAL), []) self.assertEqual( ETHERNET_FRAME.incoming(Field("Type_Length")), [ Link( Field("Type_Length_TPID"), Field("Type_Length"), NotEqual(Variable("Type_Length_TPID"), Number(0x8100, 16)), first=First("Type_Length_TPID"), ), Link(Field("TCI"), Field("Type_Length")), ], ) self.assertEqual( ETHERNET_FRAME.incoming(FINAL), [ Link( Field("Payload"), FINAL, And( GreaterEqual(Div(Length("Payload"), Number(8)), Number(46)), LessEqual(Div(Length("Payload"), Number(8)), Number(1500)), ), ) ], )
def __prove_field_positions(self) -> None: for f in self.__fields: for p, l in [(p, p[-1]) for p in self.__paths[f] if p]: path_expressions = And(*[self.__link_expression(l) for l in p]) length = self.__target_length(l) positive = If( [ ( And( self.__type_constraints(And(path_expressions, length)), path_expressions, ), GreaterEqual(length, Number(0)), ) ], TRUE, ) result = positive.forall() if result != ProofResult.sat: path_message = " -> ".join([l.target.name for l in p]) message = str(positive.simplified()).replace("\n\t", "") raise ModelError( f'negative length for field "{f.name}" on path {path_message}' f' in "{self.full_name}" ({result}: {message})' ) first = self.__target_first(l) start = If( [ ( And( self.__type_constraints(And(path_expressions, first)), path_expressions, ), GreaterEqual(first, First("Message")), ) ], TRUE, ) result = start.forall() if result != ProofResult.sat: path_message = " -> ".join([l.target.name for l in p]) message = str(start.simplified()).replace("\n\t", "") raise ModelError( f'start of field "{f.name}" on path {path_message} before' f' message start in "{self.full_name} ({result}: {message})' )
def __prove_coverage(self) -> None: """ Prove that the fields of a message cover all message bits, i.e. there are no holes in the message definition. Idea: Let f be the bits covered by the message. By definition (1) f >= Message'First and f <= Message'Last holds. For every field add a conjunction of the form (2) Not(f >= Field'First and f <= Field'Last), effectively pruning the range that this field covers from the bit range of the message. For the overall expression, prove that it is false for all f, i.e. no bits are left. """ for path in [p[:-1] for p in self.__paths[FINAL] if p]: # Calculate (1) message_range = And( GreaterEqual(Variable("f"), First("Message")), LessEqual(Variable("f"), Last("Message")), ) # Calculate (2) for all fields fields = And( *[ Not( And( GreaterEqual(Variable("f"), self.__target_first(l)), LessEqual(Variable("f"), self.__target_last(l)), ) ) for l in path ] ) # Define that the end of the last field of a path is the end of the message last_field = Equal(self.__target_last(path[-1]), Last("Message")) # Constraints for links and types path_expressions = self.__with_constraints( And(*[self.__link_expression(l) for l in path]) ) # Coverage expression must be False, i.e. no bits left coverage = Not(And(*[fields, last_field, path_expressions, message_range])) result = coverage.forall() if result != ProofResult.sat: path_message = " -> ".join([l.target.name for l in path]) message = str(coverage).replace("\n\t", "") raise ModelError( f"path {path_message} does not cover whole message" f' in "{self.full_name}" ({result}: {message})' )
def bounded_composite_setter_preconditions(message: Message, field: Field) -> Sequence[Expr]: return [ Call( "Field_Condition", [ Variable("Ctx"), NamedAggregate(("Fld", Variable(field.affixed_name))) ] + ([Variable("Length")] if common.length_dependent_condition(message) else []), ), GreaterEqual( Call("Available_Space", [Variable("Ctx"), Variable(field.affixed_name)]), Variable("Length"), ), LessEqual( Add( Call("Field_First", [Variable("Ctx"), Variable(field.affixed_name)]), Variable("Length"), ), Div(Last(const.TYPES_BIT_INDEX), Number(2)), ), Or(*[ And( *[ Call("Valid", [Variable("Ctx"), Variable(field.affixed_name)]) for field in message.fields if Variable(field.name) in l.condition.variables() ], l.condition.substituted( mapping={ Variable(field.name): Call(f"Get_{field.name}", [Variable("Ctx")]) for field in message.fields if Variable(field.name) in l.condition.variables() }), ) for l in message.incoming(field) if Last("Message") in l.length ]), Equal( Mod( Call("Field_First", [Variable("Ctx"), Variable(field.affixed_name)]), Size(const.TYPES_BYTE), ), Number(1), ), Equal( Mod(Variable("Length"), Size(const.TYPES_BYTE)), Number(0), ), ]
def test_expr_contains(self) -> None: self.assertTrue( Variable("X") in Or(Greater(Variable("Y"), Number(42)), And(TRUE, Less(Variable("X"), Number(42)))) ) self.assertFalse( Variable("Z") in Or(Greater(Variable("Y"), Number(42)), And(TRUE, Less(Variable("X"), Number(42)))) ) self.assertTrue( Less(Variable("X"), Number(42)) in Or(Greater(Variable("Y"), Number(42)), And(TRUE, Less(Variable("X"), Number(42)))) ) self.assertFalse( Less(Variable("Z"), Number(42)) in Or(Greater(Variable("Y"), Number(42)), And(TRUE, Less(Variable("X"), Number(1)))) )
def buffer_constraints(last: MathExpr) -> LogExpr: last = last.simplified() index_constraint = LessEqual(First('Buffer'), Div(Last('Types.Index_Type'), Number(2))) if last != Last('Buffer'): length_constraint = GreaterEqual( Length('Buffer'), Add(last, -First('Buffer'), Number(1))) return And(length_constraint, index_constraint) return index_constraint