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 test_message_copy() -> None: message = Message( "P.M", [Link(INITIAL, Field("F")), Link(Field("F"), FINAL)], {Field("F"): MODULAR_INTEGER}, ) assert_equal( message.copy(identifier="A.B"), Message( "A.B", [Link(INITIAL, Field("F")), Link(Field("F"), FINAL)], {Field("F"): MODULAR_INTEGER}, ), ) assert_equal( message.copy( structure=[Link(INITIAL, Field("C")), Link(Field("C"), FINAL)], types={Field("C"): RANGE_INTEGER}, ), Message( "P.M", [Link(INITIAL, Field("C")), Link(Field("C"), FINAL)], {Field("C"): RANGE_INTEGER}, ), )
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_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 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_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_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 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 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 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 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_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 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 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 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_proven() -> None: message = Message( "P.M", [Link(INITIAL, Field("F")), Link(Field("F"), FINAL)], {Field("F"): MODULAR_INTEGER}, ) assert message.proven() == message
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_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 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)
def create_expression_message() -> Message: structure = [ Link(INITIAL, Field("Payload"), length=Number(16)), Link(Field("Payload"), FINAL, Equal(Variable("Payload"), Aggregate(Number(1), Number(2)))), ] types = {Field("Payload"): Payload()} return Message("Expression.Message", structure, types)
def test_valid_first() -> None: structure = [ Link(INITIAL, Field("F1")), Link(Field("F1"), Field("F2"), first=First("F1")), Link(Field("F2"), FINAL), ] types = { Field("F1"): MODULAR_INTEGER, Field("F2"): MODULAR_INTEGER, } Message("P.M", structure, types)
def test_valid_length_reference() -> None: structure = [ Link(INITIAL, Field("F1")), Link(Field("F1"), Field("F2"), length=Mul(Number(8), Variable("F1"))), Link(Field("F2"), FINAL), ] types = { Field("F1"): MODULAR_INTEGER, Field("F2"): Opaque(), } Message("P.M", structure, types)
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 test_refinement_invalid_field_type() -> None: x = Field(ID("X", Location((20, 10)))) message = Message("P.M", [Link(INITIAL, x), Link(x, FINAL)], {x: MODULAR_INTEGER}) assert_type_error( Refinement("P", message, Field(ID("X", Location((33, 22)))), message), r'^<stdin>:33:22: model: error: invalid type of field "X" in refinement of "P.M"\n' r"<stdin>:20:10: model: info: expected field of type Opaque", )
def test_message_superfluous_type(self) -> None: t = ModularInteger("P.T", Number(2)) structure = [ Link(INITIAL, Field("X")), Link(Field("X"), FINAL), ] types = {Field("X"): t, Field("Y"): t} with self.assertRaisesRegex(ModelError, '^superfluous field "Y" in field types of "P.M"$'): Message("P.M", structure, types)
def test_write_specification_file_multiple_packages_missing_deps(tmp_path: Path) -> None: t = ModularInteger("P::T", Number(256)) u = mty.Sequence("R::U", element_type=t) u1 = mty.Sequence("Q::U1", element_type=t) v = ModularInteger("R::V", Number(65536)) links = [ Link(INITIAL, Field("Victor")), Link(Field("Victor"), Field("Uniform")), Link(Field("Uniform"), FINAL), ] fields = {Field("Victor"): v, Field("Uniform"): u} m = Message("R::M", links, fields) Model([u1, m, u, v]).write_specification_files(tmp_path) p_path, q_path, r_path = (tmp_path / Path(pkg + ".rflx") for pkg in ("p", "q", "r")) assert set(tmp_path.glob("*.rflx")) == {p_path, q_path, r_path} assert p_path.read_text() == textwrap.dedent( """\ package P is type T is mod 256; end P;""" ) assert q_path.read_text() == textwrap.dedent( """\ with P; package Q is type U1 is sequence of P::T; end Q;""" ) assert r_path.read_text() == textwrap.dedent( """\ with P; package R is type V is mod 65536; type U is sequence of P::T; type M is message Victor : R::V then Uniform with Size => Message'Last - Victor'Last; Uniform : R::U; end message; end R;""" )
def test_opaque_length_not_multiple_of_8() -> None: with pytest.raises( RecordFluxError, match=r'^<stdin>:44:3: model: error: length of opaque field "O"' " not multiple of 8 bit [(]O[)]", ): o = Field(ID("O", location=Location((44, 3)))) Message( "P.M", [Link(INITIAL, o, length=Number(68)), Link(o, FINAL)], {o: Opaque()}, )