def test_merge_message_simple_derived() -> None: assert_equal( deepcopy(M_SMPL_REF_DERI).merged(), UnprovenDerivedMessage( "P.Smpl_Ref_Deri", M_SMPL_REF, [ Link(INITIAL, Field("NR_F1"), length=Number(16)), Link(Field("NR_F3"), FINAL, Equal(Variable("NR_F3"), Variable("P.ONE"))), Link(Field("NR_F4"), FINAL), Link(Field("NR_F1"), Field("NR_F2")), Link( Field("NR_F2"), Field("NR_F3"), LessEqual(Variable("NR_F2"), Number(100)), first=First("NR_F2"), ), Link( Field("NR_F2"), Field("NR_F4"), GreaterEqual(Variable("NR_F2"), Number(200)), first=First("NR_F2"), ), ], { Field("NR_F1"): Opaque(), Field("NR_F2"): deepcopy(MODULAR_INTEGER), Field("NR_F3"): deepcopy(ENUMERATION), Field("NR_F4"): deepcopy(RANGE_INTEGER), }, ), )
def test_slice_simplified() -> None: assert_equal( Slice( Variable("Buffer"), First("Buffer"), Add(Last("Buffer"), Add(Number(21), Number(21))), ).simplified(), Slice(Variable("Buffer"), First("Buffer"), Add(Last("Buffer"), Number(42))), )
def test_conditionally_unreachable_field_enum_first() -> None: structure = [ Link(INITIAL, Field("F1")), Link(Field("F1"), Field("F2"), Greater(First("F1"), First("Message"))), Link(Field("F2"), FINAL), ] types = { Field("F1"): ENUMERATION, Field("F2"): ENUMERATION, } assert_message_model_error( structure, types, r"^" r'model: error: unreachable field "F1" in "P.M"\n' r"model: info: path 0 [(]F1[)]:\n" r'model: info: unsatisfied "F1\'First = Message\'First"\n' r'model: info: unsatisfied "F1\'First > Message\'First"\n' r'model: error: unreachable field "F2" in "P.M"\n' r"model: info: path 0 [(]F1 -> F2[)]:\n" r'model: info: unsatisfied "F1\'First = Message\'First"\n' r'model: info: unsatisfied "F1\'First > Message\'First"\n' r'model: error: unreachable field "Final" in "P.M"\n' r"model: info: path 0 [(]F1 -> F2 -> Final[)]:\n" r'model: info: unsatisfied "F1\'First = Message\'First"\n' r'model: info: unsatisfied "F1\'First > Message\'First"', )
def test_slice_simplified(self) -> None: self.assertEqual( Slice( "Buffer", First("Buffer"), Add(Last("Buffer"), Add(Number(21), Number(21))) ).simplified(), Slice("Buffer", First("Buffer"), Add(Last("Buffer"), Number(42))), )
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_merge_message_recursive() -> None: assert_equal( deepcopy(M_DBL_REF).merged(), UnprovenMessage( "P.Dbl_Ref", [ Link(INITIAL, Field("SR_NR_F1"), length=Number(16)), Link( Field("SR_NR_F3"), Field("NR_F1"), Equal(Variable("SR_NR_F3"), Variable("P.ONE")), length=Number(16), ), Link(Field("SR_NR_F4"), Field("NR_F1"), length=Number(16)), Link(Field("NR_F3"), FINAL, Equal(Variable("NR_F3"), Variable("P.ONE"))), Link(Field("NR_F4"), FINAL), Link(Field("SR_NR_F1"), Field("SR_NR_F2")), Link( Field("SR_NR_F2"), Field("SR_NR_F3"), LessEqual(Variable("SR_NR_F2"), Number(100)), first=First("SR_NR_F2"), ), Link( Field("SR_NR_F2"), Field("SR_NR_F4"), GreaterEqual(Variable("SR_NR_F2"), Number(200)), first=First("SR_NR_F2"), ), Link(Field("NR_F1"), Field("NR_F2")), Link( Field("NR_F2"), Field("NR_F3"), LessEqual(Variable("NR_F2"), Number(100)), first=First("NR_F2"), ), Link( Field("NR_F2"), Field("NR_F4"), GreaterEqual(Variable("NR_F2"), Number(200)), first=First("NR_F2"), ), ], { Field("SR_NR_F1"): Opaque(), Field("SR_NR_F2"): deepcopy(MODULAR_INTEGER), Field("SR_NR_F3"): deepcopy(ENUMERATION), Field("SR_NR_F4"): deepcopy(RANGE_INTEGER), Field("NR_F1"): Opaque(), Field("NR_F2"): deepcopy(MODULAR_INTEGER), Field("NR_F3"): deepcopy(ENUMERATION), Field("NR_F4"): deepcopy(RANGE_INTEGER), }, ), )
def substitution(self, message: Message, prefix: bool = True) -> Mapping[Name, Expr]: def prefixed(name: str) -> Expr: return Selected(Name("Ctx"), name) if prefix else Name(name) first = prefixed("First") last = prefixed("Last") cursors = prefixed("Cursors") return { **{First("Message"): first}, **{Last("Message"): last}, **{ First(f.name): Selected(Indexed(cursors, Name(f.affixed_name)), "First") for f in message.fields }, **{ Last(f.name): Selected(Indexed(cursors, Name(f.affixed_name)), "Last") for f in message.fields }, **{ Length(f.name): Add( Sub( Selected(Indexed(cursors, Name(f.affixed_name)), "Last"), Selected(Indexed(cursors, Name(f.affixed_name)), "First"), ), Number(1), ) for f in message.fields }, **{ Variable(f.name): Call( self.types.bit_length, [Selected(Indexed(cursors, Name(f.affixed_name)), f"Value.{f.name}_Value")], ) for f, t in message.types.items() if not isinstance(t, Enumeration) }, **{ Variable(f.name): Call( self.types.bit_length, [Selected(Indexed(cursors, Name(f.affixed_name)), f"Value.{f.name}_Value")], ) 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 __link_expression(self, link: Link) -> Expr: name = link.target.name return And( *[ Equal(First(name), self.__target_first(link)), Equal(Length(name), self.__target_length(link)), Equal(Last(name), self.__target_last(link)), GreaterEqual(First("Message"), Number(0)), GreaterEqual(Last("Message"), Last(name)), GreaterEqual(Last("Message"), First("Message")), Equal(Length("Message"), Add(Sub(Last("Message"), First("Message")), Number(1))), link.condition, ] )
def test_no_verification_icmp_checksum( icmp_checksum_message_value: MessageValue, icmp_message: Message) -> None: test_data = ( b"\x47\xb4\x67\x5e\x00\x00\x00\x00" b"\x4a\xfc\x0d\x00\x00\x00\x00\x00\x10\x11\x12\x13\x14\x15\x16\x17" b"\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27" b"\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37") icmp_checksum_unv = MessageValue( icmp_message.copy( structure=[ Link(l.source, l.target, condition=And(l.condition, ValidChecksum("Checksum"))) if l.target == FINAL else l for l in icmp_message.structure ], checksums={ ID("Checksum"): [ ValueRange(First("Tag"), Sub(First("Checksum"), Number(1))), Size("Checksum"), ValueRange(Add(Last("Checksum"), Number(1)), Last("Message")), ] }, ), skip_verification=True, ) icmp_checksum_message_value.set_checksum_function( {"Checksum": icmp_checksum_function}) icmp_checksum_message_value.set("Tag", "Echo_Request") icmp_checksum_message_value.set("Code_Zero", 0) icmp_checksum_message_value.set("Identifier", 5) icmp_checksum_message_value.set("Sequence_Number", 1) icmp_checksum_message_value.set("Data", test_data) icmp_checksum_unv.set_checksum_function( {"Checksum": icmp_checksum_function}) icmp_checksum_unv.set("Tag", "Echo_Request") icmp_checksum_unv.set("Code_Zero", 0) icmp_checksum_unv.set("Checksum", 0) icmp_checksum_unv.set("Identifier", 5) icmp_checksum_unv.set("Sequence_Number", 1) icmp_checksum_unv.set("Data", test_data) icmp_checksum_unv.update_checksums() assert icmp_checksum_unv.valid_message assert icmp_checksum_unv.get( "Checksum") == icmp_checksum_message_value.get("Checksum") assert icmp_checksum_unv.bytestring == icmp_checksum_message_value.bytestring
def test_incongruent_overlay() -> None: structure = [ Link(INITIAL, Field("F1")), Link(Field("F1"), Field("F2")), Link(Field("F2"), Field("F3"), first=First("F1")), Link(Field("F3"), Field("F4")), Link(Field("F4"), FINAL), ] u8 = ModularInteger("P.U8", Number(256)) u16 = ModularInteger("P.U16", Number(65536)) types = { Field("F1"): u8, Field("F2"): u8, Field("F3"): u16, Field("F4"): u16, } assert_message_model_error( structure, types, r"^" r'model: error: field "F3" not congruent with overlaid field "F1"\n' r'model: info: unsatisfied "F1\'First = Message\'First"\n' r'model: info: unsatisfied "F1\'Last = [(][(]Message\'First [+] 8[)][)] - 1"\n' r'model: info: unsatisfied "[(][(]F1\'First [+] 16[)][)] - 1 = F1\'Last"' r"$", )
def test_message_incoming(self) -> None: self.assertEqual(ETHERNET_FRAME.incoming(INITIAL), []) self.assertEqual( ETHERNET_FRAME.incoming(Field("Type_Length")), [ Link( Field("Type_Length_TPID"), Field("Type_Length"), NotEqual(Variable("Type_Length_TPID"), Number(0x8100, 16)), first=First("Type_Length_TPID"), ), Link(Field("TCI"), Field("Type_Length")), ], ) self.assertEqual( ETHERNET_FRAME.incoming(FINAL), [ Link( Field("Payload"), FINAL, And( GreaterEqual(Div(Length("Payload"), Number(8)), Number(46)), LessEqual(Div(Length("Payload"), Number(8)), Number(1500)), ), ) ], )
def parse_attribute(string: str, location: int, tokens: list) -> Attribute: if tokens[2] == 'First': return First(tokens[0]) if tokens[2] == 'Last': return Last(tokens[0]) if tokens[2] == 'Length': return Length(tokens[0]) raise ParseFatalException(string, location, 'unexpected attribute')
def create_facts(facts: Dict[Attribute, MathExpr], edge: Edge) -> Dict[Attribute, MathExpr]: facts = dict(facts) facts[Length(edge.target.name)] = edge.length.simplified(facts) facts[First(edge.target.name)] = edge.first.simplified(facts) facts[Last(edge.target.name)] = Add(edge.first, edge.length, Number(-1)).simplified(facts) return facts
def parse_attribute(string: str, location: int, tokens: ParseResults) -> Attribute: if tokens[2] == "First": return First(tokens[0]) if tokens[2] == "Last": return Last(tokens[0]) if tokens[2] == "Length": return Length(tokens[0]) raise ParseFatalException(string, location, "unexpected attribute")
def test_aggregate_substituted() -> None: assert_equal( Aggregate(First("X")).substituted( lambda x: Number(42) if x == Aggregate(First("X")) else x ), Number(42), ) assert_equal( Aggregate(First("X")).substituted(lambda x: Number(42) if x == First("X") else x), Aggregate(Number(42)), ) assert_equal( Aggregate(Variable("X")).substituted( lambda x: Variable(f"P_{x}") if isinstance(x, Variable) else (Aggregate(*(x.elements + [Variable("Y")])) if isinstance(x, Aggregate) else x) ), Aggregate(Variable("P_X"), Variable("P_Y")), )
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_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_named_aggregate_substituted() -> None: assert_equal( NamedAggregate(("First", First("X"))).substituted( lambda x: Number(42) if x == NamedAggregate(("First", First("X"))) else x ), Number(42), ) assert_equal( NamedAggregate(("First", First("X"))).substituted( lambda x: Number(42) if x == First("X") else x ), NamedAggregate(("First", Number(42))), ) assert_equal( NamedAggregate(("First", First("X"))).substituted( lambda x: Variable(f"P_{x}") if isinstance(x, Variable) else ( NamedAggregate(*[*x.elements, (ID("Last"), Last("Y"))]) if isinstance(x, NamedAggregate) else x ) ), NamedAggregate(("First", First("P_X")), ("Last", Last("P_Y"))), )
def test_pdu_fields_invalid_self_reference(self) -> None: t = ModularInteger('T', Number(2)) initial = InitialNode() n1 = Node('X', t) n2 = Node('Y', t) initial.edges = [Edge(n1, TRUE)] n1.edges = [Edge(n2, first=First('Y'))] n2.edges = [Edge(FINAL)] with self.assertRaisesRegex(ExpressionError, 'self-reference to "Y\'First"'): PDU('Z', initial).fields()
def test_attribute() -> None: assert isinstance(Size("X"), Attribute) assert isinstance(Length("X"), Attribute) assert isinstance(First("X"), Attribute) assert isinstance(Last("X"), Attribute) assert isinstance(Range("X"), Attribute) assert isinstance(Old("X"), Attribute) assert isinstance(Result("X"), Attribute) assert isinstance(Constrained("X"), Attribute) assert First("X") == First(Variable("X")) assert First("X") == First(ID("X")) assert First("X") == First(Variable(ID("X")))
def test_invalid_first_forward_reference() -> None: structure = [ Link(INITIAL, Field("F1")), Link(Field("F1"), Field("F2"), first=First("F3")), Link(Field("F2"), Field("F3")), Link(Field("F3"), FINAL), ] types = { Field("F1"): MODULAR_INTEGER, Field("F2"): MODULAR_INTEGER, Field("F3"): MODULAR_INTEGER, } assert_message_model_error( structure, types, '^model: error: subsequent field "F3" referenced$')
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_attribute_substituted() -> None: assert_equal(First("X").substituted(lambda x: Number(42) if x == First("X") else x), Number(42)) assert_equal( -First("X").substituted(lambda x: Number(42) if x == First("X") else x), Number(-42) ) assert_equal( First("X").substituted(lambda x: Call("Y") if x == Variable("X") else x), First(Call("Y")), ) assert_equal( -First("X").substituted(lambda x: Call("Y") if x == Variable("X") else x), -First(Call("Y")), ) assert_equal( -First("X").substituted( lambda x: Variable(f"P_{x}") if isinstance(x, Variable) else (Last(x.prefix) if isinstance(x, First) else x) ), -Last(Variable("P_X")), )
def test_field_coverage_1(self) -> None: foo_type = ModularInteger("P.Foo", Pow(Number(2), Number(32))) structure = [ Link(INITIAL, Field("F1")), Link(Field("F1"), Field("F2"), first=Add(First("Message"), Number(64))), Link(Field("F2"), FINAL), ] types = {Field("F1"): foo_type, Field("F2"): foo_type} with mock.patch("rflx.model.Message._Message__verify_conditions", lambda x: None): with self.assertRaisesRegex( ModelError, "^path F1 -> F2 does not cover whole message"): Message("P.M", structure, types)
def __prove_coverage(self) -> None: """ Prove that the fields of a message cover all message bits, i.e. there are no holes in the message definition. Idea: Let f be the bits covered by the message. By definition (1) f >= Message'First and f <= Message'Last holds. For every field add a conjunction of the form (2) Not(f >= Field'First and f <= Field'Last), effectively pruning the range that this field covers from the bit range of the message. For the overall expression, prove that it is false for all f, i.e. no bits are left. """ for path in [p[:-1] for p in self.__paths[FINAL] if p]: # Calculate (1) message_range = And( GreaterEqual(Variable("f"), First("Message")), LessEqual(Variable("f"), Last("Message")), ) # Calculate (2) for all fields fields = And( *[ Not( And( GreaterEqual(Variable("f"), self.__target_first(l)), LessEqual(Variable("f"), self.__target_last(l)), ) ) for l in path ] ) # Define that the end of the last field of a path is the end of the message last_field = Equal(self.__target_last(path[-1]), Last("Message")) # Constraints for links and types path_expressions = self.__with_constraints( And(*[self.__link_expression(l) for l in path]) ) # Coverage expression must be False, i.e. no bits left coverage = Not(And(*[fields, last_field, path_expressions, message_range])) result = coverage.forall() if result != ProofResult.sat: path_message = " -> ".join([l.target.name for l in path]) message = str(coverage).replace("\n\t", "") raise ModelError( f"path {path_message} does not cover whole message" f' in "{self.full_name}" ({result}: {message})' )
def __prove_field_positions(self) -> None: for f in self.__fields: for p, l in [(p, p[-1]) for p in self.__paths[f] if p]: path_expressions = And(*[self.__link_expression(l) for l in p]) length = self.__target_length(l) positive = If( [ ( And( self.__type_constraints(And(path_expressions, length)), path_expressions, ), GreaterEqual(length, Number(0)), ) ], TRUE, ) result = positive.forall() if result != ProofResult.sat: path_message = " -> ".join([l.target.name for l in p]) message = str(positive.simplified()).replace("\n\t", "") raise ModelError( f'negative length for field "{f.name}" on path {path_message}' f' in "{self.full_name}" ({result}: {message})' ) first = self.__target_first(l) start = If( [ ( And( self.__type_constraints(And(path_expressions, first)), path_expressions, ), GreaterEqual(first, First("Message")), ) ], TRUE, ) result = start.forall() if result != ProofResult.sat: path_message = " -> ".join([l.target.name for l in p]) message = str(start.simplified()).replace("\n\t", "") raise ModelError( f'start of field "{f.name}" on path {path_message} before' f' message start in "{self.full_name} ({result}: {message})' )
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_field_after_message_start(self) -> None: foo_type = ModularInteger("P.Foo", Pow(Number(2), Number(32))) structure = [ Link(INITIAL, Field("F1")), Link(Field("F1"), Field("F2"), first=Sub(First("Message"), Number(1000))), Link(Field("F2"), FINAL), ] types = {Field("F1"): foo_type, Field("F2"): foo_type} with mock.patch("rflx.model.Message._Message__verify_conditions", lambda x: None): with self.assertRaisesRegex( ModelError, '^start of field "F2" on path F1 -> F2 before' " message start"): Message("P.M", structure, types)
def test_invalid_first() -> None: structure = [ Link(INITIAL, Field("F1")), Link(Field("F1"), Field("F2"), first=Add(First("F1"), Number(8), location=Location((5, 14)))), Link(Field("F2"), FINAL), ] types = { Field("F1"): MODULAR_INTEGER, Field("F2"): MODULAR_INTEGER, } assert_message_model_error( structure, types, r'^<stdin>:5:14: model: error: invalid First for field "F2"$', )
def test_field_after_message_start(monkeypatch: Any) -> None: structure = [ Link(INITIAL, Field("F1")), Link(Field("F1"), Field("F2"), first=Sub(First("Message"), Number(1000))), Link(Field("F2"), FINAL), ] types = {Field("F1"): MODULAR_INTEGER, Field("F2"): MODULAR_INTEGER} monkeypatch.setattr(Message, "_AbstractMessage__verify_conditions", lambda x: None) assert_message_model_error( structure, types, r"^" r'model: error: negative start for field "F2" [(]F1 -> F2[)]\n' r'model: info: unsatisfied "Message\'First - 1000 >= Message\'First"' r"$", )