def test_exclusive_with_length_invalid() -> None: f1 = Field(ID("F1", Location((98, 10)))) structure = [ Link(INITIAL, f1, length=Number(32)), Link(f1, FINAL, condition=Equal(Length("F1"), Number(32), Location((10, 2)))), Link(f1, Field("F2"), condition=Equal(Length("F1"), Number(32), Location((12, 4)))), Link(Field("F2"), FINAL), ] types = { Field("F1"): Opaque(), Field("F2"): RANGE_INTEGER, } assert_message_model_error( structure, types, r"^" r'<stdin>:98:10: model: error: conflicting conditions for field "F1"\n' r"<stdin>:10:2: model: info: condition 0 [(]F1 -> Final[)]: F1\'Length = 32\n" r"<stdin>:12:4: model: info: condition 1 [(]F1 -> F2[)]: F1\'Length = 32" r"$", )
def test_aggregate_equal_array_invalid_length() -> None: magic = Field(ID("Magic", Location((3, 5)))) structure = [ Link(INITIAL, magic, length=Number(40, location=Location((19, 17)))), Link( magic, FINAL, condition=NotEqual(Variable("Magic"), Aggregate(Number(1), Number(2)), Location((17, 3))), ), ] types = { Field("Magic"): Array( "P.Arr", ModularInteger("P.Modular", Number(128), location=Location((66, 3)))), } assert_message_model_error( structure, types, r"^" r'<stdin>:17:3: model: error: contradicting condition in "P.M"\n' r'<stdin>:3:5: model: info: on path: "Magic"\n' r'<stdin>:17:3: model: info: unsatisfied "2 [*] Modular\'Length = Magic\'Length"\n' r'<stdin>:66:3: model: info: unsatisfied "Modular\'Length = 7"\n' r'<stdin>:19:17: model: info: unsatisfied "Magic\'Length = 40"', )
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(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_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 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_message_direct_predecessors(self) -> None: self.assertEqual(ETHERNET_FRAME.direct_predecessors(INITIAL), []) self.assertEqual( ETHERNET_FRAME.direct_predecessors(Field("Type_Length")), [Field("Type_Length_TPID"), Field("TCI")], ) self.assertEqual(ETHERNET_FRAME.direct_predecessors(FINAL), [Field("Payload")])
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_message_multiple_duplicate_links() -> None: t = ModularInteger("P.T", Number(2)) x = Field(ID("X", location=Location((1, 5)))) y = Field(ID("Y", location=Location((2, 5)))) structure = [ Link(INITIAL, x), Link(x, y), Link(x, FINAL, location=Location((3, 16))), Link(x, FINAL, location=Location((4, 18))), Link(y, FINAL, location=Location((5, 20))), Link(y, FINAL, location=Location((6, 22))), ] types = {Field("X"): t, Field("Y"): t} assert_message_model_error( structure, types, f'^<stdin>:1:5: model: error: duplicate link from "X" to "{FINAL.name}"\n' f"<stdin>:3:16: model: info: duplicate link\n" f"<stdin>:4:18: model: info: duplicate link\n" f'<stdin>:2:5: model: error: duplicate link from "Y" to "{FINAL.name}"\n' f"<stdin>:5:20: model: info: duplicate link\n" f"<stdin>:6:22: model: info: duplicate link", )
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_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_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_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_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_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 test_exclusive_conflict() -> None: f1 = Field(ID("F1", Location((8, 4)))) structure = [ Link(INITIAL, f1), Link(f1, FINAL, condition=Greater(Variable("F1"), Number(50), Location((10, 5)))), Link(f1, Field("F2"), condition=Less(Variable("F1"), Number(80), Location((11, 7)))), Link(Field("F2"), FINAL), ] types = { Field("F1"): RANGE_INTEGER, Field("F2"): RANGE_INTEGER, } assert_message_model_error( structure, types, r"^" r'<stdin>:8:4: model: error: conflicting conditions for field "F1"\n' r"<stdin>:10:5: model: info: condition 0 [(]F1 -> Final[)]: F1 > 50\n" r"<stdin>:11:7: model: info: condition 1 [(]F1 -> F2[)]: F1 < 80" r"$", )
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_proven() -> None: message = Message( "P.M", [Link(INITIAL, Field("F")), Link(Field("F"), FINAL)], {Field("F"): MODULAR_INTEGER}, ) assert message.proven() == message
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_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_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_message_definite_fields(self) -> None: self.assertTupleEqual( ETHERNET_FRAME.definite_fields, ( Field("Destination"), Field("Source"), Field("Type_Length_TPID"), Field("Type_Length"), Field("Payload"), ), )
def valid_fields(self) -> List[str]: return [ f for f in self.accessible_fields if (self._fields[f].set and self.__simplified( self._type.field_condition(Field(f))) == TRUE and any([ self.__simplified(i.condition) == TRUE for i in self._type.incoming(Field(f)) ]) and any([ self.__simplified(o.condition) == TRUE for o in self._type.outgoing(Field(f)) ])) ]
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_message_invalid_use_of_length_attribute() -> None: structure = [ Link(INITIAL, Field("F1")), Link(Field("F1"), FINAL, Equal(Length("F1"), Number(32), Location((400, 17)))), ] types = {Field("F1"): MODULAR_INTEGER} assert_message_model_error( structure, types, r'^<stdin>:400:17: model: error: invalid use of length attribute for "F1"$', )
def create_message(message: MessageSpec, types: Mapping[ID, Type]) -> Message: components = list(message.components) if components and components[0].name: components.insert(0, Component()) field_types: Dict[Field, Type] = {} error = RecordFluxError() for component in components: if component.name and component.type_name: type_name = qualified_type_name(component.type_name, message.package) if type_name not in types: continue field_types[Field(component.name)] = types[type_name] structure: List[Link] = [] for i, component in enumerate(components): if not component.name: error.extend([( "invalid first expression", Subsystem.PARSER, Severity.ERROR, then.first.location, ) for then in component.thens if then.first != UNDEFINED]) source_node = Field(component.name) if component.name else INITIAL if not component.thens: name = components[i + 1].name if i + 1 < len(components) else None target_node = Field(name) if name else FINAL structure.append(Link(source_node, target_node)) for then in component.thens: target_node = Field(then.name) if then.name else FINAL if then.name and target_node not in field_types.keys(): error.append( f'undefined field "{then.name}"', Subsystem.PARSER, Severity.ERROR, then.name.location if then.name else None, ) continue structure.append( Link(source_node, target_node, then.condition, then.length, then.first, then.location)) return (UnprovenMessage(message.identifier, structure, field_types, message.location, error).merged().proven())
def test_message_unused_type() -> None: t = ModularInteger("P.T", Number(2)) structure = [ Link(INITIAL, Field("X")), Link(Field("X"), FINAL), ] types = {Field("X"): t, Field(ID("Y", Location((5, 6)))): t} assert_message_model_error( structure, types, '^<stdin>:5:6: model: error: unused field "Y" in "P.M"$')
def test_message_definite_predecessors(self) -> None: self.assertTupleEqual( ETHERNET_FRAME.definite_predecessors(FINAL), ( Field("Destination"), Field("Source"), Field("Type_Length_TPID"), Field("Type_Length"), Field("Payload"), ), ) self.assertTupleEqual( ETHERNET_FRAME.definite_predecessors(Field("TCI")), (Field("Destination"), Field("Source"), Field("Type_Length_TPID"), Field("TPID")), )
def test_message_ambiguous_first_field(self) -> None: t = ModularInteger("P.T", Number(2)) structure = [ Link(INITIAL, Field("X")), Link(INITIAL, Field("Y")), Link(Field("X"), Field("Z")), Link(Field("Y"), Field("Z")), Link(Field("Z"), FINAL), ] types = {Field("X"): t, Field("Y"): t, Field("Z"): t} with self.assertRaisesRegex(ModelError, '^ambiguous first field in "P.M"$'): Message("P.M", structure, types)