from common import RostTestClass
from pychrysalide.analysis.contents import MemoryContent
from pychrysalide.analysis.scan import ContentScanner
from pychrysalide.analysis.scan import ScanOptions
from pychrysalide.analysis.scan.patterns.backends import AcismBackend
from pychrysalide.analysis.scan.patterns.backends import BitapBackend


class TestRostFuzzingFixes(RostTestClass):
    """TestCases to remember all the fixes for crashes identified by fuzzing."""

    def testEmptyPatternListWithContent(self):
        """Check no backend is run if there is no pattern to look for."""

        content = MemoryContent(b'\n')

        rule = '''
'''

        backends = [
            AcismBackend, # This one was segfaulting
            BitapBackend,
        ]

        for b in backends:

            options = ScanOptions()
            options.backend_for_data = b

            scanner = ContentScanner(rule)
            ctx = scanner.analyze(options, content)

            self.assertIsNotNone(ctx)


    def testMandatoryCondition(self):
        """Ensure a condition section exists in a rule."""

        rule = '''
rule test {

}
'''

        with self.assertRaisesRegex(ValueError, 'Unable to create content scanner'):

            scanner = ContentScanner(rule)


    def testNonExistingPattern(self):
        """Avoid to count the matches of a non-existing pattern."""

        rule = '''
rule test {

   condition:
      #badid

}
'''

        with self.assertRaisesRegex(ValueError, 'Unable to create content scanner'):

            scanner = ContentScanner(rule)


    def testNamespacesWithoutReductionCode(self):
        """Clean the code for ROST namespaces."""

        rule = '''
rule test {

   condition:
      console

}
'''

        self.check_rule_failure(rule)


    def testCallOnNonCallable(self):
        """Reject calls on non callable expressions softly."""

        rule = '''
rule test {

   condition:
      console.log().log()

}
'''

        self.check_rule_failure(rule)


    def testSelfReferencingRule(self):
        """Reject any rule referencing itself as match condition."""

        rule = '''
rule test {

   condition:
      test

}
'''

        self.check_rule_failure(rule)


    def testSelfReferencingRule(self):
        """Expect only one argument for the not operator, even in debug mode."""

        rule = '''
rule test {

   condition:
      not(0)

}
'''

        self.check_rule_success(rule)


    def testNoCommon(self):
        """Handle the case where no common item is found from an empty set."""

        rule = '''
rule test {

   bytes:
      $a = "a"

   condition:
      maxcommon($a) == 0

}
'''

        self.check_rule_success(rule)


    def testAAsAcharacter(self):
        """Consider the 'a' character as a valid lowercase character."""

        rule = '''
rule test {

   bytes:
      $a = "0000a0I0" nocase

   condition:
      $a

}
'''

        self.check_rule_failure(rule)


    def testAAsAcharacter(self):
        """Do not expect initialized trackers when there is no real defined search pattern."""

        rule = '''
rule test {

   bytes:
      $a = {[0]}

   condition:
      $a

}
'''

        with self.assertRaisesRegex(ValueError, 'Unable to create content scanner'):

            scanner = ContentScanner(rule)


    def testAllocations(self):
        """Handle big alloctions for strings in conditions with regular expressions."""

        rule = '''
rule test {

   condition:
      "%s" == "%s"

}
''' % ("0" * (256 * 2 + 8), "0" * (256 * 2 + 8))

        self.check_rule_success(rule)


    def testFileFinalAccess(self):
        """Ensure patterns found at the edges of scanned content do not crash the scanner."""

        cnt = MemoryContent(bytes([ 0 for i in range(16) ]))

        rule = '''
rule test {

   bytes:
      $a = { 00 00 00 00 00 00 00 00 }

   condition:
      $a

}
'''

        self.check_rule_success(rule, cnt)


    def testValidHexRangeMerge(self):
        """Merge valid hexadecimal ranges."""

        rule = '''
rule test {

   bytes:
      $a = { [0] ?? }

   condition:
      $a

}
'''

        with self.assertRaisesRegex(ValueError, 'Unable to create content scanner'):

            scanner = ContentScanner(rule)


        rule = '''
rule test {

   bytes:
      $a = { [2] ?? }

   condition:
      $a

}
'''

        self.check_rule_failure(rule)


    def testSmallBase64(self):
        """Handle small base64 encodings which may produce few patterns."""

        rule = '''
rule test {

   bytes:
      $a = "0" base64

   condition:
      $a

}
'''

        self.check_rule_failure(rule)


    def testCountIndex(self):
        """Ban pattern count indexes from the grammer."""

        rule = '''
rule test {

   bytes:
      $a = "1"

   condition:
      #*[0]

}
'''

        with self.assertRaisesRegex(ValueError, 'Unable to create content scanner'):

            scanner = ContentScanner(rule)