blob: 89319870d6f53fea2cb2427ee701d524ce9a5bb8 [file] [log] [blame]
Gilles Peskine24827022018-09-25 18:49:23 +02001#!/usr/bin/env python3
Gilles Peskinea3b93ff2019-06-03 11:23:56 +02002"""Test the program psa_constant_names.
Gilles Peskine24827022018-09-25 18:49:23 +02003Gather constant names from header files and test cases. Compile a C program
4to print out their numerical values, feed these numerical values to
5psa_constant_names, and check that the output is the original name.
6Return 0 if all test cases pass, 1 if the output was not always as expected,
Gilles Peskinea3b93ff2019-06-03 11:23:56 +02007or 1 (with a Python backtrace) if there was an operational error.
8"""
Gilles Peskine24827022018-09-25 18:49:23 +02009
10import argparse
Gilles Peskinea5000f12019-11-21 17:51:11 +010011from collections import namedtuple
Gilles Peskine24827022018-09-25 18:49:23 +020012import itertools
13import os
14import platform
15import re
16import subprocess
17import sys
18import tempfile
19
Gilles Peskinea0a315c2018-10-19 11:27:10 +020020class ReadFileLineException(Exception):
21 def __init__(self, filename, line_number):
22 message = 'in {} at {}'.format(filename, line_number)
23 super(ReadFileLineException, self).__init__(message)
24 self.filename = filename
25 self.line_number = line_number
26
27class read_file_lines:
Gilles Peskine54f54452019-05-27 18:31:59 +020028 # Dear Pylint, conventionally, a context manager class name is lowercase.
29 # pylint: disable=invalid-name,too-few-public-methods
Gilles Peskinea3b93ff2019-06-03 11:23:56 +020030 """Context manager to read a text file line by line.
31
32 ```
33 with read_file_lines(filename) as lines:
34 for line in lines:
35 process(line)
36 ```
37 is equivalent to
38 ```
39 with open(filename, 'r') as input_file:
40 for line in input_file:
41 process(line)
42 ```
43 except that if process(line) raises an exception, then the read_file_lines
44 snippet annotates the exception with the file name and line number.
45 """
Gilles Peskinea0a315c2018-10-19 11:27:10 +020046 def __init__(self, filename):
47 self.filename = filename
48 self.line_number = 'entry'
Gilles Peskine54f54452019-05-27 18:31:59 +020049 self.generator = None
Gilles Peskinea0a315c2018-10-19 11:27:10 +020050 def __enter__(self):
51 self.generator = enumerate(open(self.filename, 'r'))
52 return self
53 def __iter__(self):
54 for line_number, content in self.generator:
55 self.line_number = line_number
56 yield content
57 self.line_number = 'exit'
Gilles Peskine42a0a0a2019-05-27 18:29:47 +020058 def __exit__(self, exc_type, exc_value, exc_traceback):
59 if exc_type is not None:
Gilles Peskinea0a315c2018-10-19 11:27:10 +020060 raise ReadFileLineException(self.filename, self.line_number) \
Gilles Peskine42a0a0a2019-05-27 18:29:47 +020061 from exc_value
Gilles Peskinea0a315c2018-10-19 11:27:10 +020062
Gilles Peskine24827022018-09-25 18:49:23 +020063class Inputs:
Gilles Peskine8c8694c2019-11-21 19:22:45 +010064 # pylint: disable=too-many-instance-attributes
Gilles Peskinea3b93ff2019-06-03 11:23:56 +020065 """Accumulate information about macros to test.
Gilles Peskine4408dfd2019-11-21 17:16:21 +010066
Gilles Peskinea3b93ff2019-06-03 11:23:56 +020067 This includes macro names as well as information about their arguments
68 when applicable.
69 """
70
Gilles Peskine24827022018-09-25 18:49:23 +020071 def __init__(self):
Gilles Peskine2bcfc712019-11-21 19:49:26 +010072 self.all_declared = set()
Gilles Peskine24827022018-09-25 18:49:23 +020073 # Sets of names per type
74 self.statuses = set(['PSA_SUCCESS'])
75 self.algorithms = set(['0xffffffff'])
76 self.ecc_curves = set(['0xffff'])
Gilles Peskinedcaefae2019-05-16 12:55:35 +020077 self.dh_groups = set(['0xffff'])
Gilles Peskine24827022018-09-25 18:49:23 +020078 self.key_types = set(['0xffffffff'])
79 self.key_usage_flags = set(['0x80000000'])
Gilles Peskine434899f2018-10-19 11:30:26 +020080 # Hard-coded value for unknown algorithms
Darryl Green61b7f612019-02-04 16:00:21 +000081 self.hash_algorithms = set(['0x010000fe'])
Gilles Peskine434899f2018-10-19 11:30:26 +020082 self.mac_algorithms = set(['0x02ff00ff'])
Gilles Peskine882e57e2019-04-12 00:12:07 +020083 self.ka_algorithms = set(['0x30fc0000'])
84 self.kdf_algorithms = set(['0x200000ff'])
Gilles Peskine434899f2018-10-19 11:30:26 +020085 # For AEAD algorithms, the only variability is over the tag length,
86 # and this only applies to known algorithms, so don't test an
87 # unknown algorithm.
88 self.aead_algorithms = set()
Gilles Peskine24827022018-09-25 18:49:23 +020089 # Identifier prefixes
90 self.table_by_prefix = {
91 'ERROR': self.statuses,
92 'ALG': self.algorithms,
Gilles Peskine98a710c2019-11-21 18:58:36 +010093 'ECC_CURVE': self.ecc_curves,
94 'DH_GROUP': self.dh_groups,
Gilles Peskine24827022018-09-25 18:49:23 +020095 'KEY_TYPE': self.key_types,
96 'KEY_USAGE': self.key_usage_flags,
97 }
Gilles Peskine8c8694c2019-11-21 19:22:45 +010098 # Test functions
99 self.table_by_test_function = {
Gilles Peskine8fa13482019-11-25 17:10:12 +0100100 # Any function ending in _algorithm also gets added to
101 # self.algorithms.
102 'key_type': [self.key_types],
103 'ecc_key_types': [self.ecc_curves],
104 'dh_key_types': [self.dh_groups],
105 'hash_algorithm': [self.hash_algorithms],
106 'mac_algorithm': [self.mac_algorithms],
107 'cipher_algorithm': [],
108 'hmac_algorithm': [self.mac_algorithms],
109 'aead_algorithm': [self.aead_algorithms],
110 'key_derivation_algorithm': [self.kdf_algorithms],
111 'key_agreement_algorithm': [self.ka_algorithms],
112 'asymmetric_signature_algorithm': [],
113 'asymmetric_signature_wildcard': [self.algorithms],
114 'asymmetric_encryption_algorithm': [],
115 'other_algorithm': [],
Gilles Peskine8c8694c2019-11-21 19:22:45 +0100116 }
Gilles Peskine24827022018-09-25 18:49:23 +0200117 # macro name -> list of argument names
118 self.argspecs = {}
119 # argument name -> list of values
Gilles Peskine434899f2018-10-19 11:30:26 +0200120 self.arguments_for = {
121 'mac_length': ['1', '63'],
122 'tag_length': ['1', '63'],
123 }
Gilles Peskine24827022018-09-25 18:49:23 +0200124
Gilles Peskineffe2d6e2019-11-21 17:17:01 +0100125 def get_names(self, type_word):
126 """Return the set of known names of values of the given type."""
127 return {
128 'status': self.statuses,
129 'algorithm': self.algorithms,
130 'ecc_curve': self.ecc_curves,
131 'dh_group': self.dh_groups,
132 'key_type': self.key_types,
133 'key_usage': self.key_usage_flags,
134 }[type_word]
135
Gilles Peskine24827022018-09-25 18:49:23 +0200136 def gather_arguments(self):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200137 """Populate the list of values for macro arguments.
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100138
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200139 Call this after parsing all the inputs.
140 """
Gilles Peskine24827022018-09-25 18:49:23 +0200141 self.arguments_for['hash_alg'] = sorted(self.hash_algorithms)
Gilles Peskine434899f2018-10-19 11:30:26 +0200142 self.arguments_for['mac_alg'] = sorted(self.mac_algorithms)
Gilles Peskine882e57e2019-04-12 00:12:07 +0200143 self.arguments_for['ka_alg'] = sorted(self.ka_algorithms)
Gilles Peskine17542082019-01-04 19:46:31 +0100144 self.arguments_for['kdf_alg'] = sorted(self.kdf_algorithms)
Gilles Peskine434899f2018-10-19 11:30:26 +0200145 self.arguments_for['aead_alg'] = sorted(self.aead_algorithms)
Gilles Peskine24827022018-09-25 18:49:23 +0200146 self.arguments_for['curve'] = sorted(self.ecc_curves)
Gilles Peskinedcaefae2019-05-16 12:55:35 +0200147 self.arguments_for['group'] = sorted(self.dh_groups)
Gilles Peskine24827022018-09-25 18:49:23 +0200148
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200149 @staticmethod
150 def _format_arguments(name, arguments):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200151 """Format a macro call with arguments.."""
Gilles Peskine24827022018-09-25 18:49:23 +0200152 return name + '(' + ', '.join(arguments) + ')'
153
154 def distribute_arguments(self, name):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200155 """Generate macro calls with each tested argument set.
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100156
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200157 If name is a macro without arguments, just yield "name".
158 If name is a macro with arguments, yield a series of
159 "name(arg1,...,argN)" where each argument takes each possible
160 value at least once.
161 """
Gilles Peskinea0a315c2018-10-19 11:27:10 +0200162 try:
163 if name not in self.argspecs:
164 yield name
165 return
166 argspec = self.argspecs[name]
167 if argspec == []:
168 yield name + '()'
169 return
170 argument_lists = [self.arguments_for[arg] for arg in argspec]
171 arguments = [values[0] for values in argument_lists]
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200172 yield self._format_arguments(name, arguments)
Gilles Peskine54f54452019-05-27 18:31:59 +0200173 # Dear Pylint, enumerate won't work here since we're modifying
174 # the array.
175 # pylint: disable=consider-using-enumerate
Gilles Peskinea0a315c2018-10-19 11:27:10 +0200176 for i in range(len(arguments)):
177 for value in argument_lists[i][1:]:
178 arguments[i] = value
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200179 yield self._format_arguments(name, arguments)
Gilles Peskinef96ed662018-10-19 11:29:56 +0200180 arguments[i] = argument_lists[0][0]
Gilles Peskinea0a315c2018-10-19 11:27:10 +0200181 except BaseException as e:
182 raise Exception('distribute_arguments({})'.format(name)) from e
Gilles Peskine24827022018-09-25 18:49:23 +0200183
Gilles Peskine5a994c12019-11-21 16:46:51 +0100184 def generate_expressions(self, names):
185 return itertools.chain(*map(self.distribute_arguments, names))
186
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200187 _argument_split_re = re.compile(r' *, *')
188 @classmethod
189 def _argument_split(cls, arguments):
190 return re.split(cls._argument_split_re, arguments)
191
Gilles Peskine24827022018-09-25 18:49:23 +0200192 # Regex for interesting header lines.
193 # Groups: 1=macro name, 2=type, 3=argument list (optional).
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200194 _header_line_re = \
Gilles Peskine24827022018-09-25 18:49:23 +0200195 re.compile(r'#define +' +
Gilles Peskine98a710c2019-11-21 18:58:36 +0100196 r'(PSA_((?:(?:DH|ECC|KEY)_)?[A-Z]+)_\w+)' +
Gilles Peskine24827022018-09-25 18:49:23 +0200197 r'(?:\(([^\n()]*)\))?')
198 # Regex of macro names to exclude.
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200199 _excluded_name_re = re.compile(r'_(?:GET|IS|OF)_|_(?:BASE|FLAG|MASK)\Z')
Gilles Peskinec68ce962018-10-19 11:31:52 +0200200 # Additional excluded macros.
Gilles Peskine5c196fb2019-05-17 12:04:41 +0200201 _excluded_names = set([
202 # Macros that provide an alternative way to build the same
203 # algorithm as another macro.
204 'PSA_ALG_AEAD_WITH_DEFAULT_TAG_LENGTH',
205 'PSA_ALG_FULL_LENGTH_MAC',
206 # Auxiliary macro whose name doesn't fit the usual patterns for
207 # auxiliary macros.
208 'PSA_ALG_AEAD_WITH_DEFAULT_TAG_LENGTH_CASE',
Gilles Peskine5c196fb2019-05-17 12:04:41 +0200209 # Deprecated aliases.
210 'PSA_ERROR_UNKNOWN_ERROR',
211 'PSA_ERROR_OCCUPIED_SLOT',
212 'PSA_ERROR_EMPTY_SLOT',
213 'PSA_ERROR_INSUFFICIENT_CAPACITY',
Gilles Peskine19835122019-05-17 12:06:55 +0200214 'PSA_ERROR_TAMPERING_DETECTED',
Gilles Peskine5c196fb2019-05-17 12:04:41 +0200215 ])
Gilles Peskine24827022018-09-25 18:49:23 +0200216 def parse_header_line(self, line):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200217 """Parse a C header line, looking for "#define PSA_xxx"."""
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200218 m = re.match(self._header_line_re, line)
Gilles Peskine24827022018-09-25 18:49:23 +0200219 if not m:
220 return
221 name = m.group(1)
Gilles Peskine2bcfc712019-11-21 19:49:26 +0100222 self.all_declared.add(name)
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200223 if re.search(self._excluded_name_re, name) or \
224 name in self._excluded_names:
Gilles Peskine24827022018-09-25 18:49:23 +0200225 return
226 dest = self.table_by_prefix.get(m.group(2))
227 if dest is None:
228 return
229 dest.add(name)
230 if m.group(3):
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200231 self.argspecs[name] = self._argument_split(m.group(3))
Gilles Peskine24827022018-09-25 18:49:23 +0200232
233 def parse_header(self, filename):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200234 """Parse a C header file, looking for "#define PSA_xxx"."""
Gilles Peskinea0a315c2018-10-19 11:27:10 +0200235 with read_file_lines(filename) as lines:
236 for line in lines:
Gilles Peskine24827022018-09-25 18:49:23 +0200237 self.parse_header_line(line)
238
Gilles Peskine2bcfc712019-11-21 19:49:26 +0100239 _macro_identifier_re = r'[A-Z]\w+'
240 def generate_undeclared_names(self, expr):
241 for name in re.findall(self._macro_identifier_re, expr):
242 if name not in self.all_declared:
243 yield name
244
245 def accept_test_case_line(self, function, argument):
246 #pylint: disable=unused-argument
247 undeclared = list(self.generate_undeclared_names(argument))
248 if undeclared:
249 raise Exception('Undeclared names in test case', undeclared)
250 return True
251
Gilles Peskine24827022018-09-25 18:49:23 +0200252 def add_test_case_line(self, function, argument):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200253 """Parse a test case data line, looking for algorithm metadata tests."""
Gilles Peskine8c8694c2019-11-21 19:22:45 +0100254 sets = []
Gilles Peskine24827022018-09-25 18:49:23 +0200255 if function.endswith('_algorithm'):
Gilles Peskine8c8694c2019-11-21 19:22:45 +0100256 sets.append(self.algorithms)
Gilles Peskine79616682019-11-21 20:08:10 +0100257 if function == 'key_agreement_algorithm' and \
258 argument.startswith('PSA_ALG_KEY_AGREEMENT('):
259 # We only want *raw* key agreement algorithms as such, so
260 # exclude ones that are already chained with a KDF.
261 # Keep the expression as one to test as an algorithm.
262 function = 'other_algorithm'
Gilles Peskine8fa13482019-11-25 17:10:12 +0100263 sets += self.table_by_test_function[function]
Gilles Peskine2bcfc712019-11-21 19:49:26 +0100264 if self.accept_test_case_line(function, argument):
265 for s in sets:
266 s.add(argument)
Gilles Peskine24827022018-09-25 18:49:23 +0200267
268 # Regex matching a *.data line containing a test function call and
269 # its arguments. The actual definition is partly positional, but this
270 # regex is good enough in practice.
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200271 _test_case_line_re = re.compile(r'(?!depends_on:)(\w+):([^\n :][^:\n]*)')
Gilles Peskine24827022018-09-25 18:49:23 +0200272 def parse_test_cases(self, filename):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200273 """Parse a test case file (*.data), looking for algorithm metadata tests."""
Gilles Peskinea0a315c2018-10-19 11:27:10 +0200274 with read_file_lines(filename) as lines:
275 for line in lines:
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200276 m = re.match(self._test_case_line_re, line)
Gilles Peskine24827022018-09-25 18:49:23 +0200277 if m:
278 self.add_test_case_line(m.group(1), m.group(2))
279
Gilles Peskine84a45812019-11-21 19:50:33 +0100280def gather_inputs(headers, test_suites, inputs_class=Inputs):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200281 """Read the list of inputs to test psa_constant_names with."""
Gilles Peskine84a45812019-11-21 19:50:33 +0100282 inputs = inputs_class()
Gilles Peskine24827022018-09-25 18:49:23 +0200283 for header in headers:
284 inputs.parse_header(header)
285 for test_cases in test_suites:
286 inputs.parse_test_cases(test_cases)
287 inputs.gather_arguments()
288 return inputs
289
290def remove_file_if_exists(filename):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200291 """Remove the specified file, ignoring errors."""
Gilles Peskine24827022018-09-25 18:49:23 +0200292 if not filename:
293 return
294 try:
295 os.remove(filename)
Gilles Peskine54f54452019-05-27 18:31:59 +0200296 except OSError:
Gilles Peskine24827022018-09-25 18:49:23 +0200297 pass
298
Gilles Peskineb86b6d32019-11-21 17:26:10 +0100299def run_c(type_word, expressions, include_path=None, keep_c=False):
Gilles Peskine5a994c12019-11-21 16:46:51 +0100300 """Generate and run a program to print out numerical values for expressions."""
Gilles Peskineb86b6d32019-11-21 17:26:10 +0100301 if include_path is None:
302 include_path = []
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200303 if type_word == 'status':
Gilles Peskinec4cd2ad2019-02-13 18:42:53 +0100304 cast_to = 'long'
305 printf_format = '%ld'
306 else:
307 cast_to = 'unsigned long'
308 printf_format = '0x%08lx'
Gilles Peskine24827022018-09-25 18:49:23 +0200309 c_name = None
310 exe_name = None
311 try:
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200312 c_fd, c_name = tempfile.mkstemp(prefix='tmp-{}-'.format(type_word),
Gilles Peskine95ab71a2019-01-04 19:46:59 +0100313 suffix='.c',
Gilles Peskine24827022018-09-25 18:49:23 +0200314 dir='programs/psa')
315 exe_suffix = '.exe' if platform.system() == 'Windows' else ''
316 exe_name = c_name[:-2] + exe_suffix
317 remove_file_if_exists(exe_name)
318 c_file = os.fdopen(c_fd, 'w', encoding='ascii')
Gilles Peskine95ab71a2019-01-04 19:46:59 +0100319 c_file.write('/* Generated by test_psa_constant_names.py for {} values */'
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200320 .format(type_word))
Gilles Peskine95ab71a2019-01-04 19:46:59 +0100321 c_file.write('''
Gilles Peskine24827022018-09-25 18:49:23 +0200322#include <stdio.h>
323#include <psa/crypto.h>
324int main(void)
325{
326''')
Gilles Peskine5a994c12019-11-21 16:46:51 +0100327 for expr in expressions:
Gilles Peskinec4cd2ad2019-02-13 18:42:53 +0100328 c_file.write(' printf("{}\\n", ({}) {});\n'
Gilles Peskine5a994c12019-11-21 16:46:51 +0100329 .format(printf_format, cast_to, expr))
Gilles Peskine24827022018-09-25 18:49:23 +0200330 c_file.write(''' return 0;
331}
332''')
333 c_file.close()
334 cc = os.getenv('CC', 'cc')
335 subprocess.check_call([cc] +
Gilles Peskineb86b6d32019-11-21 17:26:10 +0100336 ['-I' + dir for dir in include_path] +
Gilles Peskine24827022018-09-25 18:49:23 +0200337 ['-o', exe_name, c_name])
Gilles Peskineb86b6d32019-11-21 17:26:10 +0100338 if keep_c:
Gilles Peskinecf9c18e2018-10-19 11:28:42 +0200339 sys.stderr.write('List of {} tests kept at {}\n'
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200340 .format(type_word, c_name))
Gilles Peskinecf9c18e2018-10-19 11:28:42 +0200341 else:
342 os.remove(c_name)
Gilles Peskine24827022018-09-25 18:49:23 +0200343 output = subprocess.check_output([exe_name])
344 return output.decode('ascii').strip().split('\n')
345 finally:
346 remove_file_if_exists(exe_name)
347
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200348NORMALIZE_STRIP_RE = re.compile(r'\s+')
Gilles Peskine24827022018-09-25 18:49:23 +0200349def normalize(expr):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200350 """Normalize the C expression so as not to care about trivial differences.
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100351
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200352 Currently "trivial differences" means whitespace.
353 """
Gilles Peskine5a6dc892019-11-21 16:48:07 +0100354 return re.sub(NORMALIZE_STRIP_RE, '', expr)
Gilles Peskine24827022018-09-25 18:49:23 +0200355
Gilles Peskineb86b6d32019-11-21 17:26:10 +0100356def collect_values(inputs, type_word, include_path=None, keep_c=False):
Gilles Peskinec2317112019-11-21 17:17:39 +0100357 """Generate expressions using known macro names and calculate their values.
358
359 Return a list of pairs of (expr, value) where expr is an expression and
360 value is a string representation of its integer value.
361 """
362 names = inputs.get_names(type_word)
363 expressions = sorted(inputs.generate_expressions(names))
Gilles Peskineb86b6d32019-11-21 17:26:10 +0100364 values = run_c(type_word, expressions,
365 include_path=include_path, keep_c=keep_c)
Gilles Peskinec2317112019-11-21 17:17:39 +0100366 return expressions, values
367
Gilles Peskine24609332019-11-21 17:44:21 +0100368class Tests:
369 """An object representing tests and their results."""
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100370
Gilles Peskinea5000f12019-11-21 17:51:11 +0100371 Error = namedtuple('Error',
372 ['type', 'expression', 'value', 'output'])
373
Gilles Peskine24609332019-11-21 17:44:21 +0100374 def __init__(self, options):
375 self.options = options
376 self.count = 0
377 self.errors = []
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100378
Gilles Peskine24609332019-11-21 17:44:21 +0100379 def run_one(self, inputs, type_word):
380 """Test psa_constant_names for the specified type.
Gilles Peskine24827022018-09-25 18:49:23 +0200381
Gilles Peskine24609332019-11-21 17:44:21 +0100382 Run the program on the names for this type.
383 Use the inputs to figure out what arguments to pass to macros that
384 take arguments.
385 """
386 expressions, values = collect_values(inputs, type_word,
387 include_path=self.options.include,
388 keep_c=self.options.keep_c)
389 output = subprocess.check_output([self.options.program, type_word] +
390 values)
391 outputs = output.decode('ascii').strip().split('\n')
392 self.count += len(expressions)
393 for expr, value, output in zip(expressions, values, outputs):
394 if normalize(expr) != normalize(output):
Gilles Peskinea5000f12019-11-21 17:51:11 +0100395 self.errors.append(self.Error(type=type_word,
396 expression=expr,
397 value=value,
398 output=output))
Gilles Peskine24827022018-09-25 18:49:23 +0200399
Gilles Peskine24609332019-11-21 17:44:21 +0100400 def run_all(self, inputs):
401 """Run psa_constant_names on all the gathered inputs."""
402 for type_word in ['status', 'algorithm', 'ecc_curve', 'dh_group',
403 'key_type', 'key_usage']:
404 self.run_one(inputs, type_word)
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100405
Gilles Peskine24609332019-11-21 17:44:21 +0100406 def report(self, out):
407 """Describe each case where the output is not as expected.
408
409 Write the errors to ``out``.
410 Also write a total.
411 """
Gilles Peskinea5000f12019-11-21 17:51:11 +0100412 for error in self.errors:
Gilles Peskine24609332019-11-21 17:44:21 +0100413 out.write('For {} "{}", got "{}" (value: {})\n'
Gilles Peskinea5000f12019-11-21 17:51:11 +0100414 .format(error.type, error.expression,
415 error.output, error.value))
Gilles Peskine24609332019-11-21 17:44:21 +0100416 out.write('{} test cases'.format(self.count))
417 if self.errors:
418 out.write(', {} FAIL\n'.format(len(self.errors)))
419 else:
420 out.write(' PASS\n')
Gilles Peskine24827022018-09-25 18:49:23 +0200421
Gilles Peskine69f93b52019-11-21 16:49:50 +0100422HEADERS = ['psa/crypto.h', 'psa/crypto_extra.h', 'psa/crypto_values.h']
423TEST_SUITES = ['tests/suites/test_suite_psa_crypto_metadata.data']
424
Gilles Peskine54f54452019-05-27 18:31:59 +0200425def main():
Gilles Peskine24827022018-09-25 18:49:23 +0200426 parser = argparse.ArgumentParser(description=globals()['__doc__'])
427 parser.add_argument('--include', '-I',
428 action='append', default=['include'],
429 help='Directory for header files')
Gilles Peskinecf9c18e2018-10-19 11:28:42 +0200430 parser.add_argument('--keep-c',
431 action='store_true', dest='keep_c', default=False,
432 help='Keep the intermediate C file')
433 parser.add_argument('--no-keep-c',
434 action='store_false', dest='keep_c',
435 help='Don\'t keep the intermediate C file (default)')
Gilles Peskine8f5a5012019-11-21 16:49:10 +0100436 parser.add_argument('--program',
437 default='programs/psa/psa_constant_names',
438 help='Program to test')
Gilles Peskine24827022018-09-25 18:49:23 +0200439 options = parser.parse_args()
Gilles Peskine69f93b52019-11-21 16:49:50 +0100440 headers = [os.path.join(options.include[0], h) for h in HEADERS]
441 inputs = gather_inputs(headers, TEST_SUITES)
Gilles Peskine24609332019-11-21 17:44:21 +0100442 tests = Tests(options)
443 tests.run_all(inputs)
444 tests.report(sys.stdout)
445 if tests.errors:
Gilles Peskine24827022018-09-25 18:49:23 +0200446 exit(1)
Gilles Peskine54f54452019-05-27 18:31:59 +0200447
448if __name__ == '__main__':
449 main()