def create_array_inner_message() -> Message: length_type = ModularInteger("Arrays.Length", Pow(Number(2), Number(8))) structure = [ Link(INITIAL, Field("Length")), Link(Field("Length"), Field("Payload"), length=Mul(Variable("Length"), Number(8))), Link(Field("Payload"), FINAL), ] types = {Field("Length"): length_type, Field("Payload"): Payload()} return Message("Arrays.Inner_Message", structure, types)
def test_opaque_valid_byte_aligned_dynamic_mul() -> None: Message( "P.M", [ Link(INITIAL, Field("L")), Link(Field("L"), Field("O1"), length=Mul(Number(8), Variable("L"))), Link(Field("O1"), FINAL), ], { Field("L"): MODULAR_INTEGER, Field("O1"): Opaque() }, )
def test_exclusive_prefixed_enum_valid() -> None: structure = [ Link(INITIAL, Field("F1")), Link(Field("F1"), FINAL, condition=Equal(Variable("F1"), Variable("ONE"))), Link(Field("F1"), Field("F2"), condition=Equal(Variable("F1"), Variable("P.TWO"))), Link(Field("F2"), FINAL), ] types = { Field("F1"): ENUMERATION, Field("F2"): Enumeration( "P2.Enumeration", [("ONE", Number(2)), ("TWO", Number(1))], Number(8), False, ), } Message("P.M", structure, types)
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 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 test_prefixed_message() -> None: assert_equal( UnprovenMessage( "P.M", [ Link(INITIAL, Field("F1")), Link( Field("F1"), Field("F2"), LessEqual(Variable("F1"), Number(100)), first=First("F1"), ), Link( Field("F1"), Field("F3"), GreaterEqual(Variable("F1"), Number(200)), first=First("F1"), ), Link(Field("F2"), FINAL), Link(Field("F3"), Field("F4"), length=Variable("F3")), Link(Field("F4"), FINAL), ], { Field("F1"): deepcopy(MODULAR_INTEGER), Field("F2"): deepcopy(MODULAR_INTEGER), Field("F3"): deepcopy(RANGE_INTEGER), Field("F4"): Opaque(), }, ).prefixed("X_"), UnprovenMessage( "P.M", [ Link(INITIAL, Field("X_F1")), Link( Field("X_F1"), Field("X_F2"), LessEqual(Variable("X_F1"), Number(100)), first=First("X_F1"), ), Link( Field("X_F1"), Field("X_F3"), GreaterEqual(Variable("X_F1"), Number(200)), first=First("X_F1"), ), Link(Field("X_F2"), FINAL), Link(Field("X_F3"), Field("X_F4"), length=Variable("X_F3")), Link(Field("X_F4"), FINAL), ], { Field("X_F1"): deepcopy(MODULAR_INTEGER), Field("X_F2"): deepcopy(MODULAR_INTEGER), Field("X_F3"): deepcopy(RANGE_INTEGER), Field("X_F4"): Opaque(), }, ), )
def test_dot_graph_with_double_edge(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, Greater(Variable("X"), Number(100))), Link(Field("X"), FINAL, Less(Variable("X"), Number(50))), ], 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="(X < 50, 0, ⋆)", penwidth=0, style="", width=0]; X -> intermediate_1 [arrowhead=none]; intermediate_1 -> Final [minlen=1]; intermediate_2 [color="#6f6f6f", fontcolor="#6f6f6f", fontname="Fira Code", height=0, label="(X > 100, 0, ⋆)", penwidth=0, style="", width=0]; X -> intermediate_2 [arrowhead=none]; intermediate_2 -> Final [minlen=1]; Final [fillcolor="#6f6f6f", label="", shape=circle, width="0.5"]; } """ assert_graph(create_message_graph(m), expected, tmp_path)
def test_aggregate_equal_array_valid_length() -> None: structure = [ Link(INITIAL, Field("Magic"), length=Number(14)), Link( Field("Magic"), FINAL, condition=NotEqual(Variable("Magic"), Aggregate(Number(1), Number(2))), ), ] types = { Field("Magic"): Array("P.Arr", ModularInteger("P.Modular", Number(128))), } Message("P.M", structure, types)
def test_invalid_negative_field_length_modular() -> None: structure = [ Link(INITIAL, Field("F1")), Link(Field("F1"), Field("F2"), length=Sub(Variable("F1"), Number(2))), Link(Field("F2"), FINAL), ] types = { Field("F1"): MODULAR_INTEGER, Field("F2"): Opaque(), } assert_message_model_error( structure, types, r'^model: error: negative length for field "F2" [(]F1 -> F2[)]$', )
def test_message_invalid_relation_to_aggregate() -> None: structure = [ Link(INITIAL, Field("F1"), length=Number(16)), Link( Field("F1"), FINAL, LessEqual(Variable("F1"), Aggregate(Number(1), Number(2)), Location((100, 20))), ), ] types = {Field("F1"): Opaque()} assert_message_model_error( structure, types, r'^<stdin>:100:20: model: error: invalid relation " <= " between Opaque and Aggregate$', )
def test_if_simplified(self) -> None: self.assertEqual( If( [ (Variable("X"), Number(21)), (Variable("Y"), Add(Number(21), Number(21))), (Add(Number(21), Number(21)), Variable("Z")), ] ).simplified(), If( [ (Variable("X"), Number(21)), (Variable("Y"), Number(42)), (Number(42), Variable("Z")), ] ), ) self.assertEqual(If([(TRUE, Variable("X"))]).simplified(), Variable("X"))
def test_if_variables(self) -> None: self.assertEqual( If( [ (Variable("X"), Number(21)), (Variable("Y"), Add(Number(21), Number(21))), (Add(Number(21), Number(21)), Variable("Z")), ] ).variables(), [Variable("X"), Variable("Y"), Variable("Z")], )
def test_invalid_negative_field_length_range_integer() -> None: o = Field(ID("O", location=Location((44, 3)))) structure = [ Link(INITIAL, Field("L")), Link( Field("L"), o, length=Mul(Number(8), Sub(Variable("L"), Number(50))), ), Link(o, FINAL), ] types = {Field("L"): RANGE_INTEGER, o: Opaque()} assert_message_model_error( structure, types, r'^<stdin>:44:3: model: error: negative length for field "O" [(]L -> O[)]$', )
def test_message_invalid_element_in_relation_to_aggregate() -> None: structure = [ Link(INITIAL, Field("F1")), Link( Field("F1"), FINAL, Equal(Variable("F1"), Aggregate(Number(1), Number(2)), Location((14, 7))), ), ] types = {Field("F1"): MODULAR_INTEGER} assert_message_model_error( structure, types, r'^<stdin>:14:7: model: error: invalid relation " = " ' r"between Aggregate and ModularInteger$", )
def test_message_subsequent_variable() -> None: f1 = Field("F1") f2 = Field("F2") t = ModularInteger("P.T", Pow(Number(2), Number(32))) structure = [ Link(INITIAL, f1), Link(f1, f2, Equal(Variable("F2", location=Location((1024, 57))), Number(42))), Link(f2, FINAL), ] types = {Field("F1"): t, Field("F2"): t} assert_message_model_error( structure, types, '^<stdin>:1024:57: model: error: subsequent field "F2" referenced', )
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_aggregate_inequal_valid_length() -> None: structure = [ Link(INITIAL, Field("Magic"), length=Number(40)), Link( Field("Magic"), FINAL, condition=NotEqual( Variable("Magic"), Aggregate(Number(1), Number(2), Number(3), Number(4), Number(4)), ), ), ] types = { Field("Magic"): Opaque(), } Message("P.M", structure, types)
def create_array_messages_message() -> Message: structure = [ Link(INITIAL, Field("Length")), Link(Field("Length"), Field("Messages"), length=Mul(Variable("Length"), Number(8))), Link(Field("Messages"), FINAL), ] types = { Field("Length"): ModularInteger("Arrays.Length", Pow(Number(2), Number(8))), Field("Messages"): Array("Arrays.Inner_Messages", Reference("Arrays.Inner_Message")), } return Message("Arrays.Messages_Message", structure, types)
def test_log_expr_str() -> None: assert_equal( str(And(Variable("A"), Or(Variable("B"), Variable("C")), Variable("D"))), multilinestr( """A and (B or C) and D""" ), ) assert_equal( str(AndThen(Variable("A"), OrElse(Variable("B"), Variable("C")), Variable("D"))), multilinestr( """A and then (B or else C) and then D""" ), )
def test_case_variables(self) -> None: self.assertEqual( Case( Add(Number(21), Number(21)), [ (Variable("X"), Number(21)), (Variable("Y"), Add(Number(21), Number(21))), (Add(Number(21), Number(21)), Variable("Z")), ], ).variables(), [Variable("X"), Variable("Y"), Variable("Z")], )
def unbounded_composite_setter_preconditions( message: Message, field: Field) -> Sequence[Expr]: return [ Call( "Field_Condition", [ Variable("Ctx"), NamedAggregate(("Fld", Variable(field.affixed_name))) ] + ([ Call( "Field_Length", [Variable("Ctx"), Variable(field.affixed_name)], ) ] if common.length_dependent_condition(message) else []), ), common.sufficient_space_for_field_condition( Variable(field.affixed_name)), Equal( Mod( Call("Field_First", [Variable("Ctx"), Variable(field.affixed_name)]), Size(const.TYPES_BYTE), ), Number(1), ), Equal( Mod( Call("Field_Length", [Variable("Ctx"), Variable(field.affixed_name)]), Size(const.TYPES_BYTE), ), Number(0), ), ]
def valid_predecessors_invariant() -> Expr: return AndThen( *[ If( [ ( Call( "Structural_Valid", [Indexed(Variable("Cursors"), Variable(f.affixed_name))], ), Or( *[ AndThen( Call( "Structural_Valid" if l.source in composite_fields else "Valid", [ Indexed( Variable("Cursors"), Variable(l.source.affixed_name), ) ], ), Equal( Selected( Indexed( Variable("Cursors"), Variable(f.affixed_name), ), "Predecessor", ), Variable(l.source.affixed_name), ), l.condition.substituted( substitution(message, embedded=True) ), ).simplified() for l in message.incoming(f) ] ), ) ] ) for f in message.fields if f not in message.direct_successors(INITIAL) ] )
def create_valid_message_function(message: Message) -> UnitPart: specification = FunctionSpecification("Valid_Message", "Boolean", [Parameter(["Ctx"], "Context")]) return UnitPart( [ SubprogramDeclaration( specification, [Precondition(Call("Has_Buffer", [Variable("Ctx")]))], ) ], [ ExpressionFunctionDeclaration( specification, valid_message_condition(message).substituted( common.substitution(message)).simplified(), ) ], )
def test_tlv_message_with_not_operator() -> None: message = Message( "TLV::Message_With_Not_Operator", [ Link(INITIAL, Field("Tag")), Link( Field("Tag"), Field("Length"), Not(Not(Not(NotEqual(Variable("Tag"), Variable("Msg_Data"))))), ), Link( Field("Tag"), FINAL, Not( Not( 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 }, ) model = PyRFLX(model=Model([TLV_TAG, TLV_LENGTH, message])) pkg = model.package("TLV") msg = pkg.new_message("Message_With_Not_Operator") test_bytes = b"\x01\x00\x04\x00\x00\x00\x00" msg.parse(test_bytes) assert msg.valid_message assert msg.bytestring == test_bytes
def test_opaque_length_not_multiple_of_8_dynamic() -> None: with pytest.raises( RecordFluxError, match= r'^<stdin>:44:3: model: error: length of opaque field "O" not multiple of 8 bit' " [(]L -> O[)]", ): o = Field(ID("O", location=Location((44, 3)))) Message( "P.M", [ Link(INITIAL, Field("L")), Link(Field("L"), o, length=Variable("L")), Link(o, FINAL) ], { Field("L"): MODULAR_INTEGER, o: Opaque() }, )
def test_invalid_type_condition_range_high() -> None: structure = [ Link(INITIAL, Field("F1")), Link(Field("F1"), Field("F2"), condition=Greater(Variable("F1"), Number(200))), Link(Field("F2"), FINAL), ] types = { Field("F1"): RANGE_INTEGER, Field("F2"): RANGE_INTEGER, } assert_message_model_error( structure, types, r"^" r'model: error: contradicting condition in "P.M"\n' r'model: info: on path: "F1"\n' r'model: info: unsatisfied "F1 <= 100"\n' r'model: info: unsatisfied "F1 > 200"', )
def test_invalid_type_condition_modular_lower() -> None: structure = [ Link(INITIAL, Field("F1")), Link(Field("F1"), Field("F2"), condition=Less(Variable("F1"), Number(0))), Link(Field("F2"), FINAL), ] types = { Field("F1"): MODULAR_INTEGER, Field("F2"): MODULAR_INTEGER, } assert_message_model_error( structure, types, r"^" r'model: error: contradicting condition in "P.M"\n' r'model: info: on path: "F1"\n' r'model: info: unsatisfied "F1 >= 0"\n' r'model: info: unsatisfied "F1 < 0"', )
def test_aggregate_inequal_invalid_length() -> None: structure = [ Link(INITIAL, Field("Magic"), length=Number(40)), Link( Field("Magic"), FINAL, condition=NotEqual(Variable("Magic"), Aggregate(Number(1), Number(2))), ), ] types = { Field("Magic"): Opaque(), } assert_message_model_error( structure, types, r"^" r'model: error: contradicting condition in "P.M"\n' r'model: info: on path: "Magic"\n' r'model: info: unsatisfied "2 [*] 8 = Magic\'Length"\n' r'model: info: unsatisfied "Magic\'Length = 40"', )
def test_opaque_aggregate_out_of_range() -> None: f = Field("F") with pytest.raises( RecordFluxError, match= r"^<stdin>:44:3: model: error: aggregate element out of range 0 .. 255", ): Message( "P.M", [ Link(INITIAL, f, length=Number(24)), Link( f, FINAL, condition=Equal( Variable("F"), Aggregate(Number(1), Number(2), Number(256, location=Location((44, 3)))), ), ), ], {Field("F"): Opaque()}, )
def test_case_simplified(self) -> None: self.assertEqual( Case( Add(Number(21), Number(21)), [ (Variable("X"), Number(21)), (Variable("Y"), Add(Number(21), Number(21))), (Add(Number(21), Number(21)), Variable("Z")), ], ).simplified(), Case( Number(42), [ (Variable("X"), Number(21)), (Variable("Y"), Number(42)), (Number(42), Variable("Z")), ], ), )