cert_audit: Parse more information from test suite data file

Signed-off-by: Pengyu Lv <pengyu.lv@arm.com>
diff --git a/tests/scripts/audit-validity-dates.py b/tests/scripts/audit-validity-dates.py
index 0d1425b..5e22bfc 100755
--- a/tests/scripts/audit-validity-dates.py
+++ b/tests/scripts/audit-validity-dates.py
@@ -34,6 +34,9 @@
 
 from cryptography import x509
 
+# reuse the function to parse *.data file in tests/suites/
+from generate_test_code import parse_test_data as parse_suite_data
+
 class DataType(Enum):
     CRT = 1 # Certificate
     CRL = 2 # Certificate Revocation List
@@ -129,6 +132,32 @@
         else:
             return ""
 
+    @staticmethod
+    def check_hex_string(hex_str: str) -> bool:
+        """Check if the hex string is possibly DER data."""
+        hex_len = len(hex_str)
+        # At least 6 hex char for 3 bytes: Type + Length + Content
+        if hex_len < 6:
+            return False
+        # Check if Type (1 byte) is SEQUENCE.
+        if hex_str[0:2] != '30':
+            return False
+        # Check LENGTH (1 byte) value
+        content_len = int(hex_str[2:4], base=16)
+        consumed = 4
+        if content_len in (128, 255):
+            # Indefinite or Reserved
+            return False
+        elif content_len > 127:
+            # Definite, Long
+            length_len = (content_len - 128) * 2
+            content_len = int(hex_str[consumed:consumed+length_len], base=16)
+            consumed += length_len
+        # Check LENGTH
+        if hex_len != content_len * 2 + consumed:
+            return False
+        return True
+
 class Auditor:
     """A base class for audit."""
     def __init__(self, verbose):
@@ -236,6 +265,64 @@
                               for file_name in file_names)
         return data_files
 
+class FileWrapper():
+    """
+    This a stub class of generate_test_code.FileWrapper.
+
+    This class reads the whole file to memory before iterating
+    over the lines.
+    """
+
+    def __init__(self, file_name):
+        """
+        Read the file and initialize the line number to 0.
+
+        :param file_name: File path to open.
+        """
+        with open(file_name, 'rb') as f:
+            self.buf = f.read()
+        self.buf_len = len(self.buf)
+        self._line_no = 0
+        self._line_start = 0
+
+    def __iter__(self):
+        """Make the class iterable."""
+        return self
+
+    def __next__(self):
+        """
+        This method for returning a line of the file per iteration.
+
+        :return: Line read from file.
+        """
+        # If we reach the end of the file.
+        if not self._line_start < self.buf_len:
+            raise StopIteration
+
+        line_end = self.buf.find(b'\n', self._line_start) + 1
+        if line_end > 0:
+            # Find the first LF as the end of the new line.
+            line = self.buf[self._line_start:line_end]
+            self._line_start = line_end
+            self._line_no += 1
+        else:
+            # No LF found. We are at the last line without LF.
+            line = self.buf[self._line_start:]
+            self._line_start = self.buf_len
+            self._line_no += 1
+
+        # Convert byte array to string with correct encoding and
+        # strip any whitespaces added in the decoding process.
+        return line.decode(sys.getdefaultencoding()).rstrip() + '\n'
+
+    def get_line_no(self):
+        """
+        Gives current line number.
+        """
+        return self._line_no
+
+    line_no = property(get_line_no)
+
 class SuiteDataAuditor(Auditor):
     """Class for auditing files in tests/suites/*.data"""
     def __init__(self, options):
@@ -246,27 +333,31 @@
         """Collect all files in tests/suites/*.data"""
         test_dir = self.find_test_dir()
         suites_data_folder = os.path.join(test_dir, 'suites')
-        # collect all data files in tests/suites (114 in total)
         data_files = glob.glob(os.path.join(suites_data_folder, '*.data'))
         return data_files
 
     def parse_file(self, filename: str):
-        """Parse AuditData from file."""
-        with open(filename, 'r') as f:
-            data = f.read()
+        """
+        Parse a list of AuditData from file.
+
+        :param filename: name of the file to parse.
+        :return list of AuditData parsed from the file.
+        """
         audit_data_list = []
-        # extract hex strings from the data file.
-        hex_strings = re.findall(r'"(?P<data>[0-9a-fA-F]+)"', data)
-        for hex_str in hex_strings:
-            # We regard hex string with odd number length as invaild data.
-            if len(hex_str) & 1:
-                continue
-            bytes_data = bytes.fromhex(hex_str)
-            audit_data = self.parse_bytes(bytes_data)
-            if audit_data is None:
-                continue
-            audit_data.filename = filename
-            audit_data_list.append(audit_data)
+        data_f = FileWrapper(filename)
+        for _, _, _, test_args in parse_suite_data(data_f):
+            for test_arg in test_args:
+                match = re.match(r'"(?P<data>[0-9a-fA-F]+)"', test_arg)
+                if not match:
+                    continue
+                if not X509Parser.check_hex_string(match.group('data')):
+                    continue
+                audit_data = self.parse_bytes(bytes.fromhex(match.group('data')))
+                if audit_data is None:
+                    continue
+                audit_data.filename = filename
+                audit_data_list.append(audit_data)
+
         return audit_data_list
 
 def list_all(audit_data: AuditData):
@@ -308,9 +399,6 @@
         suite_data_files = sd_auditor.default_files
 
     td_auditor.walk_all(data_files)
-    # TODO: Improve the method for auditing test suite data files
-    #       It takes 6 times longer than td_auditor.walk_all(),
-    #       typically 0.827 s VS 0.147 s.
     sd_auditor.walk_all(suite_data_files)
 
     if args.all: