def test_conditionally_unreachable_field_enum_last() -> None: structure = [ Link(INITIAL, Field("F1")), Link(Field("F1"), Field("F2"), Equal(Last("F1"), Last("Message"))), Link(Field("F2"), FINAL), ] types = { Field("F1"): ENUMERATION, Field("F2"): ENUMERATION, } assert_message_model_error( structure, types, r"^" r'model: error: unreachable field "F2" in "P.M"\n' r"model: info: path 0 [(]F1 -> F2[)]:\n" r'model: info: unsatisfied "F2\'Last = [(][(][(]F1\'Last [+] 1[)] [+] 8[)][)] - 1"\n' r'model: info: unsatisfied "Message\'Last >= F2\'Last"\n' r'model: info: unsatisfied "F1\'Last = Message\'Last"\n' r'model: error: unreachable field "Final" in "P.M"\n' r"model: info: path 0 [(]F1 -> F2 -> Final[)]:\n" r'model: info: unsatisfied "F2\'Last = [(][(][(]F1\'Last [+] 1[)] [+] 8[)][)] - 1"\n' r'model: info: unsatisfied "Message\'Last >= F2\'Last"\n' r'model: info: unsatisfied "F1\'Last = Message\'Last"', )
def test_slice_simplified() -> None: assert_equal( Slice( Variable("Buffer"), First("Buffer"), Add(Last("Buffer"), Add(Number(21), Number(21))), ).simplified(), Slice(Variable("Buffer"), First("Buffer"), Add(Last("Buffer"), Number(42))), )
def test_slice_simplified(self) -> None: self.assertEqual( Slice( "Buffer", First("Buffer"), Add(Last("Buffer"), Add(Number(21), Number(21))) ).simplified(), Slice("Buffer", First("Buffer"), Add(Last("Buffer"), Number(42))), )
def test_named_aggregate_substituted() -> None: assert_equal( NamedAggregate(("First", First("X"))).substituted( lambda x: Number(42) if x == NamedAggregate(("First", First("X"))) else x ), Number(42), ) assert_equal( NamedAggregate(("First", First("X"))).substituted( lambda x: Number(42) if x == First("X") else x ), NamedAggregate(("First", Number(42))), ) assert_equal( NamedAggregate(("First", First("X"))).substituted( lambda x: Variable(f"P_{x}") if isinstance(x, Variable) else ( NamedAggregate(*[*x.elements, (ID("Last"), Last("Y"))]) if isinstance(x, NamedAggregate) else x ) ), NamedAggregate(("First", First("P_X")), ("Last", Last("P_Y"))), )
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 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
def field_location(field_name: str, variant_id: str, variant: Variant) -> Tuple[MathExpr, MathExpr, int]: value_to_call = value_to_call_facts([(field_name, variant_id)] + variant.previous) first_byte = variant.facts[First(field_name)].to_bytes().simplified( value_to_call) last_byte = variant.facts[Last(field_name)].to_bytes().simplified( value_to_call) offset = calculate_offset(variant.facts[Last(field_name)]) return (first_byte, last_byte, offset)
def substitution(self, message: Message, prefix: bool = True) -> Mapping[Name, Expr]: def prefixed(name: str) -> Expr: return Selected(Name("Ctx"), name) if prefix else Name(name) first = prefixed("First") last = prefixed("Last") cursors = prefixed("Cursors") return { **{First("Message"): first}, **{Last("Message"): last}, **{ First(f.name): Selected(Indexed(cursors, Name(f.affixed_name)), "First") for f in message.fields }, **{ Last(f.name): Selected(Indexed(cursors, Name(f.affixed_name)), "Last") for f in message.fields }, **{ Length(f.name): Add( Sub( Selected(Indexed(cursors, Name(f.affixed_name)), "Last"), Selected(Indexed(cursors, Name(f.affixed_name)), "First"), ), Number(1), ) for f in message.fields }, **{ Variable(f.name): Call( self.types.bit_length, [Selected(Indexed(cursors, Name(f.affixed_name)), f"Value.{f.name}_Value")], ) for f, t in message.types.items() if not isinstance(t, Enumeration) }, **{ Variable(f.name): Call( self.types.bit_length, [Selected(Indexed(cursors, Name(f.affixed_name)), f"Value.{f.name}_Value")], ) for f, t in message.types.items() if isinstance(t, Enumeration) }, **{ Variable(l): Call(self.types.bit_length, [Call("Convert", [Name(l)])]) for l in itertools.chain.from_iterable( t.literals.keys() for t in message.types.values() if isinstance(t, Enumeration) ) }, }
def __link_expression(self, link: Link) -> Expr: name = link.target.name return And( *[ Equal(First(name), self.__target_first(link)), Equal(Length(name), self.__target_length(link)), Equal(Last(name), self.__target_last(link)), GreaterEqual(First("Message"), Number(0)), GreaterEqual(Last("Message"), Last(name)), GreaterEqual(Last("Message"), First("Message")), Equal(Length("Message"), Add(Sub(Last("Message"), First("Message")), Number(1))), link.condition, ] )
def test_no_verification_icmp_checksum( icmp_checksum_message_value: MessageValue, icmp_message: Message) -> None: test_data = ( b"\x47\xb4\x67\x5e\x00\x00\x00\x00" b"\x4a\xfc\x0d\x00\x00\x00\x00\x00\x10\x11\x12\x13\x14\x15\x16\x17" b"\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27" b"\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37") icmp_checksum_unv = MessageValue( icmp_message.copy( structure=[ Link(l.source, l.target, condition=And(l.condition, ValidChecksum("Checksum"))) if l.target == FINAL else l for l in icmp_message.structure ], checksums={ ID("Checksum"): [ ValueRange(First("Tag"), Sub(First("Checksum"), Number(1))), Size("Checksum"), ValueRange(Add(Last("Checksum"), Number(1)), Last("Message")), ] }, ), skip_verification=True, ) icmp_checksum_message_value.set_checksum_function( {"Checksum": icmp_checksum_function}) icmp_checksum_message_value.set("Tag", "Echo_Request") icmp_checksum_message_value.set("Code_Zero", 0) icmp_checksum_message_value.set("Identifier", 5) icmp_checksum_message_value.set("Sequence_Number", 1) icmp_checksum_message_value.set("Data", test_data) icmp_checksum_unv.set_checksum_function( {"Checksum": icmp_checksum_function}) icmp_checksum_unv.set("Tag", "Echo_Request") icmp_checksum_unv.set("Code_Zero", 0) icmp_checksum_unv.set("Checksum", 0) icmp_checksum_unv.set("Identifier", 5) icmp_checksum_unv.set("Sequence_Number", 1) icmp_checksum_unv.set("Data", test_data) icmp_checksum_unv.update_checksums() assert icmp_checksum_unv.valid_message assert icmp_checksum_unv.get( "Checksum") == icmp_checksum_message_value.get("Checksum") assert icmp_checksum_unv.bytestring == icmp_checksum_message_value.bytestring
def test_field_coverage_2(self) -> None: foo_type = ModularInteger("P.Foo", Pow(Number(2), Number(32))) structure = [ Link(INITIAL, Field("F1")), Link(Field("F1"), Field("F2")), Link(Field("F2"), Field("F4"), Greater(Variable("F1"), Number(100))), Link( Field("F2"), Field("F3"), LessEqual(Variable("F1"), Number(100)), first=Add(Last("F2"), Number(64)), ), Link(Field("F3"), Field("F4")), Link(Field("F4"), FINAL), ] types = { Field("F1"): foo_type, Field("F2"): foo_type, Field("F3"): foo_type, Field("F4"): foo_type, } with mock.patch("rflx.model.Message._Message__verify_conditions", lambda x: None): with self.assertRaisesRegex( ModelError, "^path F1 -> F2 -> F3 -> F4 does not cover whole message"): Message("P.M", structure, types)
def test_field_coverage_2(monkeypatch: Any) -> None: structure = [ Link(INITIAL, Field("F1")), Link(Field("F1"), Field("F2")), Link(Field("F2"), Field("F4"), Greater(Variable("F1"), Number(100))), Link( Field("F2"), Field("F3"), LessEqual(Variable("F1"), Number(100)), first=Add(Last("F2"), Number(64)), ), Link(Field("F3"), Field("F4")), Link(Field("F4"), FINAL), ] types = { Field("F1"): MODULAR_INTEGER, Field("F2"): MODULAR_INTEGER, Field("F3"): MODULAR_INTEGER, Field("F4"): MODULAR_INTEGER, } monkeypatch.setattr(Message, "_AbstractMessage__verify_conditions", lambda x: None) assert_message_model_error( structure, types, r"^" r"model: error: path does not cover whole message\n" r'model: info: on path: "F1"\n' r'model: info: on path: "F2"\n' r'model: info: on path: "F3"\n' r'model: info: on path: "F4"' r"$", )
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 __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 parse_attribute(string: str, location: int, tokens: list) -> Attribute: if tokens[2] == 'First': return First(tokens[0]) if tokens[2] == 'Last': return Last(tokens[0]) if tokens[2] == 'Length': return Length(tokens[0]) raise ParseFatalException(string, location, 'unexpected attribute')
def create_facts(facts: Dict[Attribute, MathExpr], edge: Edge) -> Dict[Attribute, MathExpr]: facts = dict(facts) facts[Length(edge.target.name)] = edge.length.simplified(facts) facts[First(edge.target.name)] = edge.first.simplified(facts) facts[Last(edge.target.name)] = Add(edge.first, edge.length, Number(-1)).simplified(facts) return facts
def parse_attribute(string: str, location: int, tokens: ParseResults) -> Attribute: if tokens[2] == "First": return First(tokens[0]) if tokens[2] == "Last": return Last(tokens[0]) if tokens[2] == "Length": return Length(tokens[0]) raise ParseFatalException(string, location, "unexpected attribute")
def test_valid_use_message_first_last() -> None: structure = [ Link( INITIAL, Field("Verify_Data"), length=Add(Sub(Last("Message"), First("Message")), Number(1)), ), Link(Field("Verify_Data"), FINAL), ] types = {Field("Verify_Data"): Opaque()} Message("P.M", structure, types)
def setter_preconditions(self, field: Field) -> Sequence[Expr]: return [ VALID_CONTEXT, Not(Constrained("Ctx")), Call("Has_Buffer", [Name("Ctx")]), Call("Valid_Next", [Name("Ctx"), Name(field.affixed_name)]), LessEqual( Call("Field_Last", [Name("Ctx"), Name(field.affixed_name)]), Div(Last(self.types.bit_index), Number(2)), ), ]
def test_attribute_substituted() -> None: assert_equal(First("X").substituted(lambda x: Number(42) if x == First("X") else x), Number(42)) assert_equal( -First("X").substituted(lambda x: Number(42) if x == First("X") else x), Number(-42) ) assert_equal( First("X").substituted(lambda x: Call("Y") if x == Variable("X") else x), First(Call("Y")), ) assert_equal( -First("X").substituted(lambda x: Call("Y") if x == Variable("X") else x), -First(Call("Y")), ) assert_equal( -First("X").substituted( lambda x: Variable(f"P_{x}") if isinstance(x, Variable) else (Last(x.prefix) if isinstance(x, First) else x) ), -Last(Variable("P_X")), )
def test_attribute() -> None: assert isinstance(Size("X"), Attribute) assert isinstance(Length("X"), Attribute) assert isinstance(First("X"), Attribute) assert isinstance(Last("X"), Attribute) assert isinstance(Range("X"), Attribute) assert isinstance(Old("X"), Attribute) assert isinstance(Result("X"), Attribute) assert isinstance(Constrained("X"), Attribute) assert First("X") == First(Variable("X")) assert First("X") == First(ID("X")) assert First("X") == First(Variable(ID("X")))
def setter_preconditions(field: Field) -> Sequence[Expr]: return [ Not(Constrained("Ctx")), Call("Has_Buffer", [Variable("Ctx")]), Call("Valid_Next", [Variable("Ctx"), Variable(field.affixed_name)]), LessEqual( Call("Field_Last", [Variable("Ctx"), Variable(field.affixed_name)]), Div(Last(const.TYPES_BIT_INDEX), Number(2)), ), ]
def public_substitution(self, message: Message) -> Mapping[Name, Expr]: return { **{First("Message"): Selected(Name("Ctx"), "First")}, **{Last("Message"): Selected(Name("Ctx"), "Last")}, **{ First(f.name): Call("Field_First", [Name("Ctx"), Name(f.affixed_name)]) for f in message.fields }, **{ Last(f.name): Call("Field_Last", [Name("Ctx"), Name(f.affixed_name)]) for f in message.fields }, **{ Length(f.name): Call("Field_Length", [Name("Ctx"), Name(f.affixed_name)]) for f in message.fields }, **{ Variable(f.name): Call( self.types.bit_length, [Call(f"Get_{f.name}", [Name("Ctx")])] ) for f, t in message.types.items() if not isinstance(t, Enumeration) }, **{ Variable(f.name): Call( self.types.bit_length, [Call("Convert", [Call(f"Get_{f.name}", [Name("Ctx")])])] ) for f, t in message.types.items() if isinstance(t, Enumeration) }, **{ Variable(l): Call(self.types.bit_length, [Call("Convert", [Name(l)])]) for l in itertools.chain.from_iterable( t.literals.keys() for t in message.types.values() if isinstance(t, Enumeration) ) }, }
def bounded_composite_setter_preconditions( self, message: Message, field: Field ) -> Sequence[Expr]: return [ Call( "Field_Condition", [Name("Ctx"), NamedAggregate(("Fld", Name(field.affixed_name)))] + ([Name("Length")] if length_dependent_condition(message) else []), ), GreaterEqual( Call("Available_Space", [Name("Ctx"), Name(field.affixed_name)]), Name("Length"), ), LessEqual( Add(Call("Field_First", [Name("Ctx"), Name(field.affixed_name)]), Name("Length"),), Div(Last(self.types.bit_index), Number(2)), ), Or( *[ And( *[ Call("Valid", [Name("Ctx"), Name(field.affixed_name)]) for field in message.fields if Variable(field.name) in l.condition.variables() ], l.condition.simplified( { Variable(field.name): Call(f"Get_{field.name}", [Name("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 ] ), ]
def create_ethernet_pdu() -> PDU: uint48 = ModularInteger('UINT48', Pow(Number(2), Number(48))) uint16 = RangeInteger('UINT16', Number(0), Sub(Pow(Number(2), Number(16)), Number(1)), Number(16)) payload_array = Array('Payload_Array') initial = InitialNode() destination = Node('Destination', uint48) source = Node('Source', uint48) tpid = Node('TPID', uint16) tci = Node('TCI', uint16) ether_type = Node('EtherType', uint16) payload = Node('Payload', payload_array) initial.edges = [Edge(destination)] destination.edges = [Edge(source)] source.edges = [Edge(tpid)] tpid.edges = [Edge(tci, Equal(Value('TPID'), Number(0x8100))), Edge(ether_type, NotEqual(Value('TPID'), Number(0x8100)), first=First('TPID'))] tci.edges = [Edge(ether_type)] ether_type.edges = [Edge(payload, LessEqual(Value('EtherType'), Number(1500)), Mul(LengthValue('EtherType'), Number(8))), Edge(payload, GreaterEqual(Value('EtherType'), Number(1536)), Sub(Last('Message'), Last('EtherType')))] payload.edges = [Edge(FINAL, And(GreaterEqual(Div(Length('Payload'), Number(8)), Number(46)), LessEqual(Div(Length('Payload'), Number(8)), Number(1500))))] return PDU('Ethernet.Frame', initial)
def message_length_function(variants: List[Variant]) -> Subprogram: condition_expressions: List[Tuple[LogExpr, Expr]] = [] for variant in variants: condition = variant_condition(variant) length = Add( Last(variant.previous[-1][0]), -First(variant.previous[0][0]), Number(1)).simplified(variant.facts).simplified( value_to_call_facts(variant.previous)).to_bytes().simplified() condition_expressions.append((condition, length)) return ExpressionFunction( 'Message_Length', 'Types.Length_Type', [('Buffer', 'Types.Bytes')], IfExpression(condition_expressions, 'Unreachable_Types_Length_Type'), [Precondition(And(COMMON_PRECONDITION, LogCall('Is_Valid (Buffer)')))])
def __prove_overlays(self) -> None: for f in (INITIAL, *self.__fields): for p, l in [(p, p[-1]) for p in self.__paths[f] if p]: if l.first != UNDEFINED and isinstance(l.first, First): path_expressions = And(*[self.__link_expression(l) for l in p]) overlaid = If( [(path_expressions, Equal(self.__target_last(l), Last(l.first.name)))], TRUE ) result = overlaid.forall() if result != ProofResult.sat: message = str(overlaid).replace("\n", "") raise ModelError( f'field "{f.name}" not congruent with overlaid field ' f'"{l.first.name}" in "{self.full_name}"' f" ({result}: {message})" )
def __update_simplified_mapping(self) -> None: field_values: Mapping[Name, Expr] = { **{ Variable(k): v.typeval.expr for k, v in self._fields.items() if isinstance( v.typeval, ScalarValue) and v.set }, **{ Length(k): v.typeval.size for k, v in self._fields.items() if v.set }, **{First(k): v.first for k, v in self._fields.items() if v.set}, **{Last(k): v.last for k, v in self._fields.items() if v.set}, } self._simplified_mapping = {**field_values, **self.__type_literals}
def test_invalid_first_is_last() -> None: structure = [ Link(INITIAL, Field("F1")), Link(Field("F1"), Field("F2"), first=Last(ID("F1", Location((11, 20))))), Link(Field("F2"), FINAL), ] types = { Field("F1"): MODULAR_INTEGER, Field("F2"): MODULAR_INTEGER, } assert_message_model_error( structure, types, r'^<stdin>:11:20: model: error: invalid First for field "F2"$', )
def variant_validation_function(field: Field, variant_id: str, variant: Variant) -> Subprogram: type_constraints: LogExpr = TRUE if field.type.constraints != TRUE or isinstance(field.type, Enumeration): first_byte, last_byte, offset = field_location(field.name, variant_id, variant) if field.type.constraints != TRUE: convert = Convert(field.type.base_name, 'Buffer', first_byte, last_byte, offset) type_constraints = field.type.constraints.simplified( {Value(field.type.name): convert}) if isinstance(field.type, Enumeration): type_constraints = And( type_constraints, LogCall((f'Valid_{field.type.name} (Buffer ({first_byte}' f' .. {last_byte}), {offset})'))) value_to_call = value_to_call_facts([(field.name, variant_id)] + variant.previous) return ExpressionFunction( f'Valid_{field.name}_{variant_id}', 'Boolean', [('Buffer', 'Types.Bytes')], And( LogCall( f'Valid_{variant.previous[-1][0]}_{variant.previous[-1][1]} (Buffer)' ) if variant.previous else TRUE, And( And( buffer_constraints(variant.facts[Last( field.name)].to_bytes()).simplified(value_to_call), variant.condition.simplified( variant.facts).simplified(value_to_call)), type_constraints)).simplified(), [Precondition(COMMON_PRECONDITION)])