def test_message_type_message() -> None: simple_structure = [ Link(INITIAL, Field("Bar")), Link(Field("Bar"), Field("Baz")), Link(Field("Baz"), FINAL), ] simple_types = { Field("Bar"): ModularInteger("Message_Type.T", Number(256)), Field("Baz"): ModularInteger("Message_Type.T", Number(256)), } simple_message = Message("Message_Type.Simple_PDU", simple_structure, simple_types) structure = [ Link(INITIAL, Field("Foo")), Link(Field("Foo"), Field("Bar"), LessEqual(Variable("Foo"), Number(30, 16))), Link(Field("Foo"), Field("Baz"), Greater(Variable("Foo"), Number(30, 16))), Link(Field("Bar"), Field("Baz")), Link(Field("Baz"), FINAL), ] types = { **simple_types, **{Field("Foo"): ModularInteger("Message_Type.T", Number(256))}, } message = Message("Message_Type.PDU", structure, types) empty_message = Message("Message_Type.Empty_PDU", [], {}) assert_messages_files( [f"{TESTDIR}/message_type.rflx"], [message, simple_message, empty_message] )
def test_array_aggregate_invalid_element_type() -> None: inner = Message( "P.I", [Link(INITIAL, Field("F")), Link(Field("F"), FINAL)], {Field("F"): MODULAR_INTEGER}, ) array_type = Array("P.Array", inner) f = Field("F") with pytest.raises( RecordFluxError, match=r"^<stdin>:90:10: model: error: invalid array element type" ' "P.I" for aggregate comparison$', ): Message( "P.M", [ Link(INITIAL, f, length=Number(18)), Link( f, FINAL, condition=Equal( Variable("F"), Aggregate(Number(1), Number(2), Number(64)), Location((90, 10)), ), ), ], {Field("F"): array_type}, )
def setter_postconditions( self, message: Message, field: Field, field_type: Type ) -> Sequence[Expr]: return [ *[ Call("Invalid", [Name("Ctx"), Name(p.affixed_name)]) for p in message.successors(field) if p != FINAL ], *self.common.valid_path_to_next_field_condition(message, field, field_type), *[ Equal(e, Old(e)) for e in [ Selected("Ctx", "Buffer_First"), Selected("Ctx", "Buffer_Last"), Selected("Ctx", "First"), Call("Predecessor", [Name("Ctx"), Name(field.affixed_name)]), Call("Valid_Next", [Name("Ctx"), Name(field.affixed_name)]), ] + [ Call(f"Get_{p.name}", [Name("Ctx")]) for p in message.definite_predecessors(field) if isinstance(message.types[p], Scalar) ] ], ]
def test_message_proven() -> None: message = Message( "P.M", [Link(INITIAL, Field("F")), Link(Field("F"), FINAL)], {Field("F"): MODULAR_INTEGER}, ) assert message.proven() == message
def context_cursor_unchanged( message: model.Message, field: model.Field, predecessors: bool ) -> List[Expr]: lower: model.Field upper: model.Field if predecessors: if len(message.predecessors(field)) == 0: return [] lower = message.fields[0] upper = message.fields[message.fields.index(field) - 1] else: if len(message.successors(field)) == 0: return [] lower = message.fields[message.fields.index(field) + 1] upper = message.fields[-1] return [ ForAllIn( "F", ValueRange( lower=Variable(lower.affixed_name), upper=Variable(upper.affixed_name), type_identifier=ID("Field"), ), Equal( Call( "Context_Cursors_Index", [ Call( "Context_Cursors", [Variable("Ctx")], ), Variable("F"), ], ), Call( "Context_Cursors_Index", [ Old( Call( "Context_Cursors", [Variable("Ctx")], ) ), Variable("F"), ], ), ), ) ]
def test_type_derivation_refinements() -> None: message_foo = Message( "Test.Foo", [Link(INITIAL, Field("Baz"), length=Number(48)), Link(Field("Baz"), FINAL)], {Field("Baz"): Opaque()}, ) message_bar = DerivedMessage("Test.Bar", message_foo) assert_refinements_string( """ package Test is type Foo is message null then Baz with Length => 48; Baz : Opaque; end message; for Foo use (Baz => Foo); type Bar is new Foo; for Bar use (Baz => Bar); end Test; """, [ Refinement("Test", message_foo, Field("Baz"), message_foo), Refinement("Test", message_bar, Field("Baz"), message_bar), ], )
def test_write_specification_files_missing_deps(tmp_path: Path) -> None: s = ModularInteger("P::S", Number(65536)) t = ModularInteger("P::T", Number(256)) v = mty.Sequence("P::V", element_type=t) m = Message("P::M", [Link(INITIAL, Field("Foo")), Link(Field("Foo"), FINAL)], {Field("Foo"): t}) Model([s, v, m]).write_specification_files(tmp_path) expected_path = tmp_path / Path("p.rflx") assert list(tmp_path.glob("*.rflx")) == [expected_path] assert expected_path.read_text() == textwrap.dedent( """\ package P is type S is mod 65536; type T is mod 256; type V is sequence of P::T; type M is message Foo : P::T; end message; end P;""" )
def test_valid_use_message_length() -> None: structure = [ Link(INITIAL, Field("Verify_Data"), length=Length("Message")), Link(Field("Verify_Data"), FINAL), ] types = {Field("Verify_Data"): Opaque()} Message("P.M", structure, types)
def test_no_contradiction_multi() -> None: structure = [ Link(INITIAL, Field("F0")), Link(Field("F0"), Field("F1"), condition=Equal(Variable("F0"), Number(1))), Link(Field("F0"), Field("F2"), condition=Equal(Variable("F0"), Number(2))), Link(Field("F1"), Field("F3")), Link(Field("F2"), Field("F3")), Link(Field("F3"), Field("F4"), condition=Equal(Variable("F0"), Number(1))), Link(Field("F3"), Field("F5"), condition=Equal(Variable("F0"), Number(2))), Link(Field("F4"), FINAL), Link(Field("F5"), FINAL), ] types = { Field("F0"): RANGE_INTEGER, Field("F1"): RANGE_INTEGER, Field("F2"): RANGE_INTEGER, Field("F3"): RANGE_INTEGER, Field("F4"): RANGE_INTEGER, Field("F5"): RANGE_INTEGER, } Message("P.M", structure, types)
def _expand_message_links(self, message: Message, messages: Dict[ID, Message]) -> Message: """Split disjunctions in link conditions.""" structure = [] for link in message.structure: conditions = self._expand_expression(link.condition.simplified()) if len(conditions) == 1: structure.append(link) continue for condition in conditions: structure.append( Link( link.source, link.target, condition, link.size, link.first, condition.location, )) types = { f: self._replace_messages(t, messages) for f, t in message.types.items() } return message.copy(structure=structure, types=types)
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_dot_graph(tmp_path: Path) -> None: f_type = ModularInteger("P::T", Pow(Number(2), Number(32))) m = Message( "P::M", structure=[Link(INITIAL, Field("X")), Link(Field("X"), FINAL)], types={Field("X"): f_type}, ) expected = """ digraph "P::M" { graph [bgcolor="#00000000", pad="0.1", ranksep="0.1 equally", splines=true, truecolor=true]; edge [color="#6f6f6f", fontcolor="#6f6f6f", fontname="Fira Code", penwidth="2.5"]; node [color="#6f6f6f", fillcolor="#009641", fontcolor="#ffffff", fontname=Arimo, shape=box, style="rounded,filled", width="1.5"]; Initial [fillcolor="#ffffff", label="", shape=circle, width="0.5"]; X; intermediate_0 [color="#6f6f6f", fontcolor="#6f6f6f", fontname="Fira Code", height=0, label="(⊤, 32, ⋆)", penwidth=0, style="", width=0]; Initial -> intermediate_0 [arrowhead=none]; intermediate_0 -> X [minlen=1]; intermediate_1 [color="#6f6f6f", fontcolor="#6f6f6f", fontname="Fira Code", height=0, label="(⊤, 0, ⋆)", penwidth=0, style="", width=0]; X -> intermediate_1 [arrowhead=none]; intermediate_1 -> Final [minlen=1]; Final [fillcolor="#6f6f6f", label="", shape=circle, width="0.5"]; } """ assert_graph(create_message_graph(m), expected, tmp_path)
def test_graph_object() -> None: f_type = ModularInteger("P::T", Pow(Number(2), Number(32))) m = Message( "P::M", structure=[Link(INITIAL, Field("X")), Link(Field("X"), FINAL)], types={Field("X"): f_type}, ) g = create_message_graph(m) assert [(e.get_source(), e.get_destination()) for e in g.get_edges()] == [ ("Initial", "intermediate_0"), ("intermediate_0", "X"), ("X", "intermediate_1"), ("intermediate_1", "Final"), ] assert [n.get_name() for n in g.get_nodes()] == [ "graph", "edge", "node", "Initial", "X", "intermediate_0", "intermediate_1", "Final", ]
def valid_path_to_next_field_condition( message: model.Message, field: model.Field, prefix: str ) -> Sequence[Expr]: return [ If( [ ( l.condition.substituted(substitution(message, public=True, prefix=prefix)) .simplified() .ada_expr(), And( Equal( Call( "Predecessor", [Variable("Ctx"), Variable(l.target.affixed_name)], ), Variable(field.affixed_name), ), Call( "Valid_Next", [Variable("Ctx"), Variable(l.target.affixed_name)], ) if l.target != model.FINAL else TRUE, ), ) ] ) for l in message.outgoing(field) if l.target != model.FINAL ]
def test_array_aggregate_out_of_range() -> None: array_type = Array("P.Array", ModularInteger("P.Element", Number(64))) f = Field("F") with pytest.raises( RecordFluxError, match= r"^<stdin>:44:3: model: error: aggregate element out of range 0 .. 63", ): Message( "P.M", [ Link(INITIAL, f, length=Number(18)), Link( f, FINAL, condition=Equal( Variable("F"), Aggregate(Number(1), Number(2), Number(64, location=Location((44, 3)))), ), ), ], {Field("F"): array_type}, )
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 test_dot_graph_with_condition() -> None: f_type = ModularInteger("P.T", Pow(Number(2), Number(32))) m = Message( "P.M", structure=[ Link(INITIAL, Field("F1")), Link(Field("F1"), FINAL, Greater(Variable("F1"), Number(100))), ], types={Field("F1"): f_type}, ) expected = """ digraph "P.M" { graph [ranksep="0.8 equally", splines=ortho]; edge [color="#6f6f6f", fontcolor="#6f6f6f", fontname="Fira Code"]; node [color="#6f6f6f", fillcolor="#009641", fontcolor="#ffffff", fontname=Arimo, shape=box, style="rounded,filled", width="1.5"]; Initial [fillcolor="#ffffff", label="", shape=circle, width="0.5"]; F1; Initial -> F1 [xlabel="(⊤, 32, ⋆)"]; F1 -> Final [xlabel="(F1 > 100, 0, ⋆)"]; Final [fillcolor="#6f6f6f", label="", shape=circle, width="0.5"]; } """ assert_graph(Graph(m), expected)
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 create_array_message() -> Message: length_type = ModularInteger("Arrays.Length", Pow(Number(2), Number(8))) modular_type = ModularInteger("Arrays.Modular_Integer", Pow(Number(2), Number(16))) modular_vector_type = Array("Arrays.Modular_Vector", modular_type) range_type = RangeInteger("Arrays.Range_Integer", Number(1), Number(100), Number(8)) range_vector_type = Array("Arrays.Range_Vector", range_type) enum_type = Enumeration( "Arrays.Enumeration", { "ZERO": Number(0), "ONE": Number(1), "TWO": Number(2) }, Number(8), False, ) enum_vector_type = Array("Arrays.Enumeration_Vector", enum_type) av_enum_type = Enumeration( "Arrays.AV_Enumeration", { "AV_ZERO": Number(0), "AV_ONE": Number(1), "AV_TWO": Number(2) }, Number(8), True, ) av_enum_vector_type = Array("Arrays.AV_Enumeration_Vector", av_enum_type) structure = [ Link(INITIAL, Field("Length")), Link(Field("Length"), Field("Modular_Vector"), length=Mul(Variable("Length"), Number(8))), Link(Field("Modular_Vector"), Field("Range_Vector"), length=Number(16)), Link(Field("Range_Vector"), Field("Enumeration_Vector"), length=Number(16)), Link(Field("Enumeration_Vector"), Field("AV_Enumeration_Vector"), length=Number(16)), Link(Field("AV_Enumeration_Vector"), FINAL), ] types = { Field("Length"): length_type, Field("Modular_Vector"): modular_vector_type, Field("Range_Vector"): range_vector_type, Field("Enumeration_Vector"): enum_vector_type, Field("AV_Enumeration_Vector"): av_enum_vector_type, } return Message("Arrays.Message", structure, types)
def create_tlv_message() -> Message: tag_type = Enumeration("TLV.Tag", { "Msg_Data": Number(1), "Msg_Error": Number(3) }, Number(2), False) length_type = ModularInteger("TLV.Length", Pow(Number(2), Number(14))) structure = [ Link(INITIAL, Field("Tag")), Link(Field("Tag"), Field("Length"), Equal(Variable("Tag"), Variable("Msg_Data"))), Link(Field("Tag"), FINAL, Equal(Variable("Tag"), Variable("Msg_Error"))), Link(Field("Length"), Field("Value"), length=Mul(Variable("Length"), Number(8))), Link(Field("Value"), FINAL), ] types = { Field("Tag"): tag_type, Field("Length"): length_type, Field("Value"): Payload() } return Message("TLV.Message", structure, types)
def test_derived_message_incorrect_base_name() -> None: with pytest.raises( RecordFluxError, match= '^<stdin>:40:8: model: error: unexpected format of type name "M"$' ): DerivedMessage("P.M", Message("M", [], {}, location=Location((40, 8))))
def test_message_incorrect_name() -> None: with pytest.raises( RecordFluxError, match= '^<stdin>:10:8: model: error: unexpected format of type name "M"$' ): Message("M", [], {}, Location((10, 8)))
def test_opaque_not_byte_aligned_dynamic() -> None: with pytest.raises( RecordFluxError, match= r'^<stdin>:44:3: model: error: opaque field "O2" not aligned to' r" 8 bit boundary [(]L1 -> O1 -> L2 -> O2[)]", ): o2 = Field(ID("O2", location=Location((44, 3)))) Message( "P.M", [ Link(INITIAL, Field("L1")), Link( Field("L1"), Field("O1"), length=Variable("L1"), condition=Equal(Mod(Variable("L1"), Number(8)), Number(0)), ), Link(Field("O1"), Field("L2")), Link(Field("L2"), o2, length=Number(128)), Link(o2, FINAL), ], { Field("L1"): MODULAR_INTEGER, Field("L2"): ModularInteger("P.T", Number(4)), Field("O1"): Opaque(), o2: Opaque(), }, )
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_message_missing_type(self) -> None: structure = [ Link(INITIAL, Field("X")), Link(Field("X"), FINAL), ] with self.assertRaisesRegex(ModelError, '^missing type for field "X" of "P.M"$'): Message("P.M", structure, {})
def has_size_dependent_condition(message: model.Message, field: model.Field = None) -> bool: field_sizes = {expr.Size(f.name) for f in message.fields} links = message.outgoing(field) if field else message.structure return any( size in field_sizes for link in links for size in link.condition.findall(lambda x: isinstance(x, expr.Size)) )
def valid_message_condition(message: Message, field: Field = INITIAL, structural: bool = False) -> Expr: if not message.outgoing(field): return TRUE return Or(*[ l.condition if l.target == FINAL else AndThen( Call( "Structural_Valid" if structural and isinstance( message.types[l.target], Composite) else "Valid", [Variable("Ctx" ), Variable(l.target.affixed_name)], ), l.condition, valid_message_condition(message, l.target, structural), ) for l in message.outgoing(field) ])
def test_invalid_message_field_type() -> None: with pytest.raises(AssertionError, match=r"rflx/model.py"): Message( "P.M", [Link(INITIAL, Field("F")), Link(Field("F"), FINAL)], {Field("F"): NewType("T")}, )
def test_message_in_message() -> None: length = ModularInteger("Message_In_Message.Length", Pow(Number(2), Number(16))) length_value = Message( "Message_In_Message.Length_Value", [ Link(INITIAL, Field("Length")), Link(Field("Length"), Field("Value"), length=Mul(Number(8), Variable("Length"))), Link(Field("Value"), FINAL), ], {Field("Length"): length, Field("Value"): Opaque()}, ) derived_length_value = DerivedMessage("Message_In_Message.Derived_Length_Value", length_value) message = Message( "Message_In_Message.Message", [ Link(INITIAL, Field("Foo_Length")), Link(Field("Foo_Value"), Field("Bar_Length")), Link(Field("Bar_Value"), FINAL), Link( Field("Foo_Length"), Field("Foo_Value"), length=Mul(Variable("Foo_Length"), Number(8)), ), Link( Field("Bar_Length"), Field("Bar_Value"), length=Mul(Variable("Bar_Length"), Number(8)), ), ], { Field("Foo_Length"): length, Field("Foo_Value"): Opaque(), Field("Bar_Length"): length, Field("Bar_Value"): Opaque(), }, ) derived_message = DerivedMessage("Message_In_Message.Derived_Message", message) assert_messages_files( [f"{TESTDIR}/message_in_message.rflx"], [length_value, derived_length_value, message, derived_message], )
def test_tlv_message_with_not_operator_exhausting() -> None: message = Message( "TLV::Message_With_Not_Operator_Exhausting", [ Link(INITIAL, Field("Tag")), Link( Field("Tag"), Field("Length"), Not(Not(Not(NotEqual(Variable("Tag"), Variable("Msg_Data"))))), ), Link( Field("Tag"), FINAL, reduce( lambda acc, f: f(acc), [Not, Not] * 16, Not( Or( Not( Not( Equal(Variable("Tag"), Variable("Msg_Data")))), Not(Equal(Variable("Tag"), Variable("Msg_Error"))), )), ), ), Link(Field("Length"), Field("Value"), size=Mul(Variable("Length"), Number(8))), Link(Field("Value"), FINAL), ], { Field("Tag"): TLV_TAG, Field("Length"): TLV_LENGTH, Field("Value"): OPAQUE }, ) with pytest.raises( FatalError, match=re.escape( "failed to simplify complex expression `not (not (not (not " "(not (not (not (not (not (not (not (not (not (not (not (not " "(not (not (not (not (not (not (not (not (not (not (not (not " "(not (not (not (not (not (not (not (Tag = TLV::Msg_Data))\n" " " "or not (Tag = TLV::Msg_Error))))))))))))))))))))))))))))))))))` " "after `16` iterations, best effort: " "`not (not (not (not (not (not (not (not (not (not (not (not (not " "(not (not (not (not (Tag = TLV::Msg_Data\n" " or Tag /= TLV::Msg_Error)))))))))))))))))`"), ): model = PyRFLX(model=Model([TLV_TAG, TLV_LENGTH, message])) pkg = model.package("TLV") msg = pkg.new_message("Message_With_Not_Operator_Exhausting") test_bytes = b"\x01\x00\x04\x00\x00\x00\x00" msg.parse(test_bytes)