def fixture_icmp_checksum_message_first(icmp_message: model.Message) -> pyrflx.MessageValue: return pyrflx.MessageValue( icmp_message.copy( structure=[ model.Link( l.source, l.target, condition=expr.And(l.condition, expr.ValidChecksum("Checksum")), ) if l.target == model.FINAL else l for l in icmp_message.structure ], checksums={ ID("Checksum"): [ expr.ValueRange( expr.First("Message"), expr.Sub(expr.First("Checksum"), expr.Number(1)) ), expr.Size("Checksum"), expr.ValueRange( expr.Add(expr.Last("Checksum"), expr.Number(1)), expr.Last("Message") ), ] }, ) )
def test_named_aggregate_rflx_expr() -> None: assert ada.NamedAggregate( ("X", ada.Number(1)), (ada.ValueRange(ada.Number(2), ada.Number(3)), ada.Variable("Y")), ).rflx_expr() == expr.NamedAggregate( ("X", expr.Number(1)), (expr.ValueRange(expr.Number(2), expr.Number(3)), expr.Variable("Y")), )
def size(pair: FieldPair) -> expr.Expr: max_size = 2**29 - 1 if isinstance(pair.target_type, (Opaque, Sequence)): if isinstance(pair.source_type, Integer): if pair.source_type.last.value <= max_size: return expr.Mul(expr.Variable(pair.source.name), expr.Number(8)) return expr.Number( draw(st.integers(min_value=1, max_value=max_size).map(lambda x: x * 8)) ) return expr.UNDEFINED
def modular_integers( draw: Draw, unique_identifiers: ty.Generator[ID, None, None], multiple_of_8: bool = False, align_to_8: int = 0, ) -> ModularInteger: return ModularInteger( next(unique_identifiers), expr.Pow(expr.Number(2), expr.Number(draw(sizes(multiple_of_8, align_to_8)))), )
def range_integers( draw: Draw, unique_identifiers: ty.Generator[ID, None, None], multiple_of_8: bool = False, align_to_8: int = 0, ) -> RangeInteger: size = draw(sizes(multiple_of_8, align_to_8)) max_value = min(2**size - 1, 2**63 - 1) first = draw(st.integers(min_value=0, max_value=max_value)) last = draw(st.integers(min_value=first, max_value=max_value)) return RangeInteger( next(unique_identifiers), expr.Number(first), expr.Number(last), expr.Number(size) )
def enumerations( draw: Draw, unique_identifiers: ty.Generator[ID, None, None], multiple_of_8: bool = False, align_to_8: int = 0, ) -> Enumeration: @st.composite def literal_identifiers(_: ty.Callable[[], object]) -> str: assert unique_identifiers return str(next(unique_identifiers).name) size = draw(sizes(multiple_of_8, align_to_8)) literals = draw( st.lists( st.tuples( literal_identifiers(), st.builds( expr.Number, st.integers(min_value=0, max_value=min(2**size - 1, 2**63 - 1)) ), ), unique_by=(lambda x: x[0], lambda x: x[1]), # type: ignore[no-any-return] min_size=1, max_size=2**size, ) ) return Enumeration( next(unique_identifiers), literals, expr.Number(size), draw(st.booleans()) if len(literals) < 2**size else False, )
def test_substitution_relation_aggregate( relation: Callable[[expr.Expr, expr.Expr], expr.Relation], left: expr.Expr, right: expr.Expr ) -> None: equal_call = expr.Call( "Equal", [ expr.Variable("Ctx"), expr.Variable("F_Value"), expr.Aggregate( expr.Val(expr.Variable("Types.Byte"), expr.Number(1)), expr.Val(expr.Variable("Types.Byte"), expr.Number(2)), ), ], ) assert_equal( relation(left, right).substituted(common.substitution(TLV_MESSAGE)), equal_call if relation == expr.Equal else expr.Not(equal_call), )
def field_size(field: model.Field) -> expr.Expr: if public: return expr.Call( "Field_Size", [expr.Variable("Ctx"), expr.Variable(field.affixed_name)] ) return expr.Add( expr.Sub( expr.Selected(expr.Indexed(cursors, expr.Variable(field.affixed_name)), "Last"), expr.Selected(expr.Indexed(cursors, expr.Variable(field.affixed_name)), "First"), ), expr.Number(1), )
def test_verified(tmp_path: Path) -> None: m1 = model.Message( "P::M", [ model.Link(model.INITIAL, model.Field("A")), model.Link(model.Field("A"), model.FINAL), ], { model.Field("A"): model.ModularInteger("P::T", expr.Pow(expr.Number(2), expr.Number(8))) }, ) m2 = model.Message( "P::M", [ model.Link(model.INITIAL, model.Field("B")), model.Link(model.Field("B"), model.FINAL), ], { model.Field("B"): model.ModularInteger("P::T", expr.Pow(expr.Number(2), expr.Number(8))) }, ) m3 = model.Message( "P::M", [ model.Link(model.INITIAL, model.Field("A")), model.Link(model.Field("A"), model.FINAL), ], { model.Field("A"): model.ModularInteger("P::T", expr.Pow(expr.Number(2), expr.Number(16))) }, ) cache.CACHE_DIR = tmp_path c = cache.Cache() assert not c.is_verified(m1) assert not c.is_verified(m2) assert not c.is_verified(m3) c.add_verified(m1) assert c.is_verified(m1) assert not c.is_verified(m2) assert not c.is_verified(m3) c.add_verified(m2) assert c.is_verified(m1) assert c.is_verified(m2) assert not c.is_verified(m3) c.add_verified(m3) assert c.is_verified(m1) assert c.is_verified(m2) assert c.is_verified(m3) c.add_verified(m1) assert c.is_verified(m1) assert c.is_verified(m2) assert c.is_verified(m3)
def constraints(self, name: str, proof: bool = False, same_package: bool = True) -> ty.Sequence[expr.Expr]: if proof: return [ expr.Less(expr.Variable(name), self._modulus, location=self.location), expr.GreaterEqual(expr.Variable(name), expr.Number(0), location=self.location), expr.Equal(expr.Size(name), self.size, location=self.location), ] raise NotImplementedError
def condition(pair: FieldPair) -> expr.Expr: if isinstance(pair.source_type, Integer): first = pair.source_type.first.value last = pair.source_type.last.value if last - first > 0: return expr.Equal( expr.Variable(pair.source.name), expr.Number(draw(st.integers(min_value=first, max_value=last))), ) elif isinstance(pair.source_type, Enumeration) and len(pair.source_type.literals) > 1: return expr.Equal( expr.Variable(pair.source.name), expr.Variable( list(pair.source_type.literals.keys())[ draw(st.integers(min_value=0, max_value=len(pair.source_type.literals) - 1)) ] ), ) return expr.TRUE
def test_ipv4_parsing_ipv4(ipv4_packet_value: pyrflx.MessageValue) -> None: with open(CAPTURED_DIR / "ipv4_udp.raw", "rb") as file: msg_as_bytes: bytes = file.read() ipv4_packet_value.parse(msg_as_bytes) assert ipv4_packet_value.get("Version") == 4 assert ipv4_packet_value.get("IHL") == 5 assert ipv4_packet_value.get("DSCP") == 0 assert ipv4_packet_value.get("ECN") == 0 assert ipv4_packet_value.get("Total_Length") == 44 assert ipv4_packet_value.get("Identification") == 1 assert ipv4_packet_value.get("Flag_R") == "False" assert ipv4_packet_value.get("Flag_DF") == "False" assert ipv4_packet_value.get("Flag_MF") == "False" assert ipv4_packet_value.get("Fragment_Offset") == 0 assert ipv4_packet_value.get("TTL") == 64 assert ipv4_packet_value.get("Protocol") == "IPv4::P_UDP" assert ipv4_packet_value.get("Header_Checksum") == int("7CBE", 16) assert ipv4_packet_value.get("Source") == int("7f000001", 16) assert ipv4_packet_value.get("Destination") == int("7f000001", 16) assert ipv4_packet_value._fields["Payload"].typeval.size == expr.Number(192)
def __init__(self, identifier: StrID, modulus: expr.Expr, location: Location = None) -> None: super().__init__(identifier, expr.UNDEFINED, location) modulus_num = modulus.simplified() if not isinstance(modulus_num, expr.Number): self.error.extend([( f'modulus of "{self.name}" contains variable', Subsystem.MODEL, Severity.ERROR, self.location, )], ) return modulus_int = int(modulus_num) # https://github.com/Componolit/RecordFlux/issues/1077 # size of integers is limited to 63bits if modulus_int > 2**const.MAX_SCALAR_SIZE: self.error.extend([( f'modulus of "{self.name}" exceeds limit (2**{const.MAX_SCALAR_SIZE})', Subsystem.MODEL, Severity.ERROR, modulus.location, )], ) if modulus_int == 0 or (modulus_int & (modulus_int - 1)) != 0: self.error.extend([( f'modulus of "{self.name}" not power of two', Subsystem.MODEL, Severity.ERROR, self.location, )], ) self._modulus = modulus self._size = expr.Number((modulus_int - 1).bit_length())
def __init__( self, identifier: StrID, first: expr.Expr, last: expr.Expr, size: expr.Expr, location: Location = None, ) -> None: super().__init__(identifier, size, location) first_num = first.simplified() last_num = last.simplified() size_num = size.simplified() if not isinstance(first_num, expr.Number): self.error.extend([( f'first of "{self.name}" contains variable', Subsystem.MODEL, Severity.ERROR, self.location, )], ) if not isinstance(last_num, expr.Number): self.error.extend([( f'last of "{self.name}" contains variable', Subsystem.MODEL, Severity.ERROR, self.location, )], ) return if int(last_num) >= 2**const.MAX_SCALAR_SIZE: self.error.extend([( f'last of "{self.name}" exceeds limit (2**{const.MAX_SCALAR_SIZE} - 1)', Subsystem.MODEL, Severity.ERROR, self.location, )], ) if not isinstance(size_num, expr.Number): self.error.extend([( f'size of "{self.name}" contains variable', Subsystem.MODEL, Severity.ERROR, self.location, )], ) if self.error.check(): return assert isinstance(first_num, expr.Number) assert isinstance(last_num, expr.Number) assert isinstance(size_num, expr.Number) if first_num < expr.Number(0): self.error.extend([( f'first of "{self.name}" negative', Subsystem.MODEL, Severity.ERROR, self.location, )], ) if first_num > last_num: self.error.extend([( f'range of "{self.name}" negative', Subsystem.MODEL, Severity.ERROR, self.location, )], ) if int(last_num).bit_length() > int(size_num): self.error.extend([( f'size of "{self.name}" too small', Subsystem.MODEL, Severity.ERROR, self.location, )], ) # https://github.com/Componolit/RecordFlux/issues/1077 # size of integers is limited to 63bits if int(size_num) > const.MAX_SCALAR_SIZE: self.error.extend([( f'size of "{self.name}" exceeds limit (2**{const.MAX_SCALAR_SIZE})', Subsystem.MODEL, Severity.ERROR, self.location, )], ) self._first_expr = first self._first = first_num self._last_expr = last self._last = last_num
def numbers(draw: Draw, min_value: int = 0, max_value: int = None) -> expr.Number: return expr.Number(draw(st.integers(min_value=min_value, max_value=max_value)))
def substitution_facts( message: model.Message, prefix: str, embedded: bool = False, public: bool = False, target_type: Optional[ID] = const.TYPES_BASE_INT, ) -> Mapping[expr.Name, expr.Expr]: def prefixed(name: str) -> expr.Expr: return expr.Variable(ID("Ctx") * name) if not embedded else expr.Variable(name) first = prefixed("First") last = expr.Call("Written_Last", [expr.Variable("Ctx")]) if public else prefixed("Written_Last") cursors = prefixed("Cursors") def field_first(field: model.Field) -> expr.Expr: if public: return expr.Call( "Field_First", [expr.Variable("Ctx"), expr.Variable(field.affixed_name)] ) return expr.Selected(expr.Indexed(cursors, expr.Variable(field.affixed_name)), "First") def field_last(field: model.Field) -> expr.Expr: if public: return expr.Call( "Field_Last", [expr.Variable("Ctx"), expr.Variable(field.affixed_name)] ) return expr.Selected(expr.Indexed(cursors, expr.Variable(field.affixed_name)), "Last") def field_size(field: model.Field) -> expr.Expr: if public: return expr.Call( "Field_Size", [expr.Variable("Ctx"), expr.Variable(field.affixed_name)] ) return expr.Add( expr.Sub( expr.Selected(expr.Indexed(cursors, expr.Variable(field.affixed_name)), "Last"), expr.Selected(expr.Indexed(cursors, expr.Variable(field.affixed_name)), "First"), ), expr.Number(1), ) def parameter_value(parameter: model.Field, parameter_type: model.Type) -> expr.Expr: if isinstance(parameter_type, model.Enumeration): if embedded: return expr.Call("To_Base_Integer", [expr.Variable(parameter.name)]) return expr.Call("To_Base_Integer", [expr.Variable("Ctx" * parameter.identifier)]) if isinstance(parameter_type, model.Scalar): if embedded: return expr.Variable(parameter.name) return expr.Variable("Ctx" * parameter.identifier) assert False, f'unexpected type "{type(parameter_type).__name__}"' def field_value(field: model.Field, field_type: model.Type) -> expr.Expr: if isinstance(field_type, model.Enumeration): if public: return expr.Call( "To_Base_Integer", [expr.Call(f"Get_{field.name}", [expr.Variable("Ctx")])] ) return expr.Selected( expr.Indexed(cursors, expr.Variable(field.affixed_name)), "Value", ) if isinstance(field_type, model.Scalar): if public: return expr.Call(f"Get_{field.name}", [expr.Variable("Ctx")]) return expr.Selected( expr.Indexed(cursors, expr.Variable(field.affixed_name)), "Value", ) if isinstance(field_type, model.Composite): return expr.Variable(field.name) assert False, f'unexpected type "{type(field_type).__name__}"' def type_conversion(expression: expr.Expr) -> expr.Expr: return expr.Call(target_type, [expression]) if target_type else expression return { **{expr.First("Message"): type_conversion(first)}, **{expr.Last("Message"): type_conversion(last)}, **{expr.Size("Message"): type_conversion(expr.Add(last, -first, expr.Number(1)))}, **{expr.First(f.name): type_conversion(field_first(f)) for f in message.fields}, **{expr.Last(f.name): type_conversion(field_last(f)) for f in message.fields}, **{expr.Size(f.name): type_conversion(field_size(f)) for f in message.fields}, **{ expr.Variable(p.identifier): type_conversion(parameter_value(p, t)) for p, t in message.parameter_types.items() }, **{ expr.Variable(f.name): type_conversion(field_value(f, t)) for f, t in message.field_types.items() }, **{ expr.Literal(l): type_conversion(expr.Call("To_Base_Integer", [expr.Variable(l)])) for t in message.types.values() if isinstance(t, model.Enumeration) and t != model.BOOLEAN for l in t.literals.keys() }, **{ expr.Literal(t.package * l): type_conversion( expr.Call("To_Base_Integer", [expr.Variable(prefix * t.package * l)]) ) for t in message.types.values() if isinstance(t, model.Enumeration) and t != model.BOOLEAN for l in t.literals.keys() }, # https://github.com/Componolit/RecordFlux/issues/276 **{expr.ValidChecksum(f): expr.TRUE for f in message.checksums}, }
def message_structure_invariant( message: model.Message, prefix: str, link: model.Link = None, embedded: bool = False ) -> Expr: def prefixed(name: str) -> expr.Expr: return expr.Selected(expr.Variable("Ctx"), name) if not embedded else expr.Variable(name) if not link: return message_structure_invariant( message, prefix, message.outgoing(model.INITIAL)[0], embedded ) source = link.source target = link.target if target == model.FINAL: return TRUE field_type = message.types[target] condition = link.condition.substituted(substitution(message, prefix, embedded)).simplified() size = ( field_type.size if isinstance(field_type, model.Scalar) else link.size.substituted( substitution(message, prefix, embedded, target_type=const.TYPES_BIT_LENGTH) ).simplified() ) first = ( prefixed("First") if source == model.INITIAL else link.first.substituted( substitution(message, prefix, embedded, target_type=const.TYPES_BIT_INDEX) ) .substituted( mapping={ expr.UNDEFINED: expr.Add( expr.Selected( expr.Indexed(prefixed("Cursors"), expr.Variable(source.affixed_name)), "Last", ), expr.Number(1), ) } ) .simplified() ) invariant = [ message_structure_invariant(message, prefix, l, embedded) for l in message.outgoing(target) ] return If( [ ( AndThen( Call( "Structural_Valid", [Indexed(prefixed("Cursors").ada_expr(), Variable(target.affixed_name))], ), *([condition.ada_expr()] if condition != expr.TRUE else []), ), AndThen( Equal( Add( Sub( Selected( Indexed( prefixed("Cursors").ada_expr(), Variable(target.affixed_name), ), "Last", ), Selected( Indexed( prefixed("Cursors").ada_expr(), Variable(target.affixed_name), ), "First", ), ), Number(1), ), size.ada_expr(), ), Equal( Selected( Indexed( prefixed("Cursors").ada_expr(), Variable(target.affixed_name), ), "Predecessor", ), Variable(source.affixed_name), ), Equal( Selected( Indexed( prefixed("Cursors").ada_expr(), Variable(target.affixed_name), ), "First", ), first.ada_expr(), ), *[i for i in invariant if i != TRUE], ), ) ] )
def test_derivation_spec() -> None: generator = generate(DERIVATION_MODEL) assert_specification(generator) def test_derivation_body() -> None: generator = generate(DERIVATION_MODEL) assert_body(generator) @pytest.mark.parametrize( "left,right", [ (expr.Variable("Value"), expr.Aggregate(expr.Number(1), expr.Number(2))), (expr.Aggregate(expr.Number(1), expr.Number(2)), expr.Variable("Value")), ], ) @pytest.mark.parametrize("relation", [expr.Equal, expr.NotEqual]) def test_substitution_relation_aggregate( relation: Callable[[expr.Expr, expr.Expr], expr.Relation], left: expr.Expr, right: expr.Expr ) -> None: equal_call = expr.Call( "Equal", [ expr.Variable("Ctx"), expr.Variable("F_Value"), expr.Aggregate( expr.Val(expr.Variable("Types.Byte"), expr.Number(1)), expr.Val(expr.Variable("Types.Byte"), expr.Number(2)),
def value_count(self) -> expr.Number: return self.last - self.first + expr.Number(1)
def element_size(self) -> expr.Expr: return expr.Number(8)
def test_number_rflx_expr() -> None: assert ada.Number(42).rflx_expr() == expr.Number(42)
def type_(self) -> rty.Type: return rty.Private(str(self.identifier)) OPAQUE = Opaque( location=Location((0, 0), Path(str(const.BUILTINS_PACKAGE)), (0, 0))) INTERNAL_TYPES = { OPAQUE.identifier: OPAQUE, } BOOLEAN = Enumeration( const.BUILTINS_PACKAGE * "Boolean", [ (ID("False", Location((0, 0), Path(str(const.BUILTINS_PACKAGE)), (0, 0))), expr.Number(0)), (ID("True", Location((0, 0), Path(str(const.BUILTINS_PACKAGE)), (0, 0))), expr.Number(1)), ], expr.Number(1), always_valid=False, location=rty.BOOLEAN.location, ) BUILTIN_TYPES = { BOOLEAN.identifier: BOOLEAN, } BUILTIN_LITERALS = {l for t in BUILTIN_TYPES.values() for l in t.literals}
def last(self) -> expr.Number: modulus = self.modulus.simplified() assert isinstance(modulus, expr.Number) return modulus - expr.Number(1)
def first(self) -> expr.Number: return expr.Number(0)
("X::Y", ID("X::Y")), ("X2::Y2", ID("X2::Y2")), ("X_Y::Z", ID("X_Y::Z")), ("X_Y_3::Z_4", ID("X_Y_3::Z_4")), ], ) def test_qualified_identifier(string: str, expected: ID) -> None: actual = parse_id(string, lang.GrammarRule.qualified_identifier_rule) assert actual == expected assert actual.location @pytest.mark.parametrize( "string,expected", [ ("1000", expr.Number(1000)), ("1_000", expr.Number(1000)), ("16#6664#", expr.Number(26212)), ("16#66_64#", expr.Number(26212)), ("-1000", expr.Number(-1000)), ("-1_000", expr.Number(-1000)), ("-16#6664#", expr.Number(-26212)), ("-16#66_64#", expr.Number(-26212)), ], ) def test_expression_numeric_literal(string: str, expected: expr.Expr) -> None: actual = parse_math_expression(string, extended=False) assert actual == expected assert actual.location
def value_count(self) -> expr.Number: if self.always_valid: size_num = self.size.simplified() assert isinstance(size_num, expr.Number) return expr.Number(2**int(size_num)) return expr.Number(len(self.literals))
def test_consistency_specification_parsing_generation(tmp_path: Path) -> None: tag = Enumeration( "Test::Tag", [("Msg_Data", expr.Number(1)), ("Msg_Error", expr.Number(3))], expr.Number(8), always_valid=False, ) length = ModularInteger("Test::Length", expr.Pow(expr.Number(2), expr.Number(16))) message = Message( "Test::Message", [ Link(INITIAL, Field("Tag")), Link( Field("Tag"), Field("Length"), expr.Equal(expr.Variable("Tag"), expr.Variable("Msg_Data")), ), Link(Field("Tag"), FINAL, expr.Equal(expr.Variable("Tag"), expr.Variable("Msg_Error"))), Link( Field("Length"), Field("Value"), size=expr.Mul(expr.Variable("Length"), expr.Number(8)), ), Link(Field("Value"), FINAL), ], { Field("Tag"): tag, Field("Length"): length, Field("Value"): OPAQUE }, skip_proof=True, ) session = Session( "Test::Session", "A", "C", [ State( "A", declarations=[], actions=[stmt.Read("X", expr.Variable("M"))], transitions=[ Transition("B"), ], ), State( "B", declarations=[ decl.VariableDeclaration("Z", BOOLEAN.identifier, expr.Variable("Y")), ], actions=[], transitions=[ Transition( "C", condition=expr.And( expr.Equal(expr.Variable("Z"), expr.TRUE), expr.Equal(expr.Call("G", [expr.Variable("F")]), expr.TRUE), ), description="rfc1149.txt+45:4-47:8", ), Transition("A"), ], description="rfc1149.txt+51:4-52:9", ), State("C"), ], [ decl.VariableDeclaration("M", "Test::Message"), decl.VariableDeclaration("Y", BOOLEAN.identifier, expr.FALSE), ], [ decl.ChannelDeclaration("X", readable=True, writable=True), decl.TypeDeclaration(Private("Test::T")), decl.FunctionDeclaration("F", [], "Test::T"), decl.FunctionDeclaration("G", [decl.Argument("P", "Test::T")], BOOLEAN.identifier), ], [BOOLEAN, OPAQUE, tag, length, message], ) model = Model(types=[BOOLEAN, OPAQUE, tag, length, message], sessions=[session]) model.write_specification_files(tmp_path) p = parser.Parser() p.parse(tmp_path / "test.rflx") parsed_model = p.create_model() assert parsed_model.types == model.types assert parsed_model.sessions == model.sessions assert parsed_model == model
def test_aggregate_rflx_expr() -> None: assert ada.Aggregate(ada.Number(1), ada.Number(2)).rflx_expr() == expr.Aggregate( expr.Number(1), expr.Number(2))