blob: 9795e05c074fe34ce7cf980e3523709903b91ee5 [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
Bence Szépkúti1e148272020-08-07 13:07:28 +020010# Copyright The Mbed TLS Contributors
Bence Szépkútic7da1fe2020-05-26 01:54:15 +020011# SPDX-License-Identifier: Apache-2.0
12#
13# Licensed under the Apache License, Version 2.0 (the "License"); you may
14# not use this file except in compliance with the License.
15# You may obtain a copy of the License at
16#
17# http://www.apache.org/licenses/LICENSE-2.0
18#
19# Unless required by applicable law or agreed to in writing, software
20# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
21# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22# See the License for the specific language governing permissions and
23# limitations under the License.
Bence Szépkúti700ee442020-05-26 00:33:31 +020024
Gilles Peskine24827022018-09-25 18:49:23 +020025import argparse
Gilles Peskinea5000f12019-11-21 17:51:11 +010026from collections import namedtuple
Gilles Peskine24827022018-09-25 18:49:23 +020027import itertools
28import os
Gilles Peskine24827022018-09-25 18:49:23 +020029import re
30import subprocess
31import sys
Gilles Peskine2adebc82020-12-11 00:30:53 +010032
33import scripts_path # pylint: disable=unused-import
34from mbedtls_dev import c_build_helper
Gilles Peskine24827022018-09-25 18:49:23 +020035
Gilles Peskine6f7ba5f2021-03-10 00:50:18 +010036class PSAMacroEnumerator:
37 """Information about constructors of various PSA Crypto types.
38
39 This includes macro names as well as information about their arguments
40 when applicable.
41
42 This class only provides ways to enumerate expressions that evaluate to
43 values of the covered types. Derived classes are expected to populate
44 the set of known constructors of each kind, as well as populate
45 `self.arguments_for` for arguments that are not of a kind that is
46 enumerated here.
47 """
48
49 def __init__(self):
50 """Set up an empty set of known constructor macros.
51 """
52 self.statuses = set()
53 self.algorithms = set()
54 self.ecc_curves = set()
55 self.dh_groups = set()
56 self.key_types = set()
57 self.key_usage_flags = set()
58 self.hash_algorithms = set()
59 self.mac_algorithms = set()
60 self.ka_algorithms = set()
61 self.kdf_algorithms = set()
62 self.aead_algorithms = set()
63 # macro name -> list of argument names
64 self.argspecs = {}
65 # argument name -> list of values
66 self.arguments_for = {
67 'mac_length': [],
68 'min_mac_length': [],
69 'tag_length': [],
70 'min_tag_length': [],
71 }
72
73 def gather_arguments(self):
74 """Populate the list of values for macro arguments.
75
76 Call this after parsing all the inputs.
77 """
78 self.arguments_for['hash_alg'] = sorted(self.hash_algorithms)
79 self.arguments_for['mac_alg'] = sorted(self.mac_algorithms)
80 self.arguments_for['ka_alg'] = sorted(self.ka_algorithms)
81 self.arguments_for['kdf_alg'] = sorted(self.kdf_algorithms)
82 self.arguments_for['aead_alg'] = sorted(self.aead_algorithms)
83 self.arguments_for['curve'] = sorted(self.ecc_curves)
84 self.arguments_for['group'] = sorted(self.dh_groups)
85
86 @staticmethod
87 def _format_arguments(name, arguments):
88 """Format a macro call with arguments.."""
89 return name + '(' + ', '.join(arguments) + ')'
90
91 _argument_split_re = re.compile(r' *, *')
92 @classmethod
93 def _argument_split(cls, arguments):
94 return re.split(cls._argument_split_re, arguments)
95
96 def distribute_arguments(self, name):
97 """Generate macro calls with each tested argument set.
98
99 If name is a macro without arguments, just yield "name".
100 If name is a macro with arguments, yield a series of
101 "name(arg1,...,argN)" where each argument takes each possible
102 value at least once.
103 """
104 try:
105 if name not in self.argspecs:
106 yield name
107 return
108 argspec = self.argspecs[name]
109 if argspec == []:
110 yield name + '()'
111 return
112 argument_lists = [self.arguments_for[arg] for arg in argspec]
113 arguments = [values[0] for values in argument_lists]
114 yield self._format_arguments(name, arguments)
115 # Dear Pylint, enumerate won't work here since we're modifying
116 # the array.
117 # pylint: disable=consider-using-enumerate
118 for i in range(len(arguments)):
119 for value in argument_lists[i][1:]:
120 arguments[i] = value
121 yield self._format_arguments(name, arguments)
122 arguments[i] = argument_lists[0][0]
123 except BaseException as e:
124 raise Exception('distribute_arguments({})'.format(name)) from e
125
126 def generate_expressions(self, names):
127 """Generate expressions covering values constructed from the given names.
128
129 `names` can be any iterable collection of macro names.
130
131 For example:
132 * ``generate_expressions(['PSA_ALG_CMAC', 'PSA_ALG_HMAC'])``
133 generates ``'PSA_ALG_CMAC'`` as well as ``'PSA_ALG_HMAC(h)'`` for
134 every known hash algorithm ``h``.
135 * ``macros.generate_expressions(macros.key_types)`` generates all
136 key types.
137 """
138 return itertools.chain(*map(self.distribute_arguments, names))
139
Gilles Peskinea0a315c2018-10-19 11:27:10 +0200140class ReadFileLineException(Exception):
141 def __init__(self, filename, line_number):
142 message = 'in {} at {}'.format(filename, line_number)
143 super(ReadFileLineException, self).__init__(message)
144 self.filename = filename
145 self.line_number = line_number
146
147class read_file_lines:
Gilles Peskine54f54452019-05-27 18:31:59 +0200148 # Dear Pylint, conventionally, a context manager class name is lowercase.
149 # pylint: disable=invalid-name,too-few-public-methods
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200150 """Context manager to read a text file line by line.
151
152 ```
153 with read_file_lines(filename) as lines:
154 for line in lines:
155 process(line)
156 ```
157 is equivalent to
158 ```
159 with open(filename, 'r') as input_file:
160 for line in input_file:
161 process(line)
162 ```
163 except that if process(line) raises an exception, then the read_file_lines
164 snippet annotates the exception with the file name and line number.
165 """
Gilles Peskine49af2d32019-12-06 19:20:13 +0100166 def __init__(self, filename, binary=False):
Gilles Peskinea0a315c2018-10-19 11:27:10 +0200167 self.filename = filename
168 self.line_number = 'entry'
Gilles Peskine54f54452019-05-27 18:31:59 +0200169 self.generator = None
Gilles Peskine49af2d32019-12-06 19:20:13 +0100170 self.binary = binary
Gilles Peskinea0a315c2018-10-19 11:27:10 +0200171 def __enter__(self):
Gilles Peskine49af2d32019-12-06 19:20:13 +0100172 self.generator = enumerate(open(self.filename,
173 'rb' if self.binary else 'r'))
Gilles Peskinea0a315c2018-10-19 11:27:10 +0200174 return self
175 def __iter__(self):
176 for line_number, content in self.generator:
177 self.line_number = line_number
178 yield content
179 self.line_number = 'exit'
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200180 def __exit__(self, exc_type, exc_value, exc_traceback):
181 if exc_type is not None:
Gilles Peskinea0a315c2018-10-19 11:27:10 +0200182 raise ReadFileLineException(self.filename, self.line_number) \
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200183 from exc_value
Gilles Peskinea0a315c2018-10-19 11:27:10 +0200184
Gilles Peskine6f7ba5f2021-03-10 00:50:18 +0100185class InputsForTest(PSAMacroEnumerator):
Gilles Peskine8c8694c2019-11-21 19:22:45 +0100186 # pylint: disable=too-many-instance-attributes
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200187 """Accumulate information about macros to test.
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100188
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200189 This includes macro names as well as information about their arguments
190 when applicable.
191 """
192
Gilles Peskine24827022018-09-25 18:49:23 +0200193 def __init__(self):
Gilles Peskine6f7ba5f2021-03-10 00:50:18 +0100194 super().__init__()
Gilles Peskine2bcfc712019-11-21 19:49:26 +0100195 self.all_declared = set()
Gilles Peskine24827022018-09-25 18:49:23 +0200196 # Sets of names per type
Gilles Peskine6f7ba5f2021-03-10 00:50:18 +0100197 self.statuses.add('PSA_SUCCESS')
198 self.algorithms.add('0xffffffff')
199 self.ecc_curves.add('0xff')
200 self.dh_groups.add('0xff')
201 self.key_types.add('0xffff')
202 self.key_usage_flags.add('0x80000000')
203
Bence Szépkúti4af65602020-12-08 11:10:21 +0100204 # Hard-coded values for unknown algorithms
205 #
206 # These have to have values that are correct for their respective
207 # PSA_ALG_IS_xxx macros, but are also not currently assigned and are
208 # not likely to be assigned in the near future.
Gilles Peskine6f7ba5f2021-03-10 00:50:18 +0100209 self.hash_algorithms.add('0x020000fe') # 0x020000ff is PSA_ALG_ANY_HASH
210 self.mac_algorithms.add('0x03007fff')
211 self.ka_algorithms.add('0x09fc0000')
212 self.kdf_algorithms.add('0x080000ff')
Gilles Peskine434899f2018-10-19 11:30:26 +0200213 # For AEAD algorithms, the only variability is over the tag length,
214 # and this only applies to known algorithms, so don't test an
215 # unknown algorithm.
Gilles Peskine6f7ba5f2021-03-10 00:50:18 +0100216
Gilles Peskine24827022018-09-25 18:49:23 +0200217 # Identifier prefixes
218 self.table_by_prefix = {
219 'ERROR': self.statuses,
220 'ALG': self.algorithms,
Gilles Peskine98a710c2019-11-21 18:58:36 +0100221 'ECC_CURVE': self.ecc_curves,
222 'DH_GROUP': self.dh_groups,
Gilles Peskine24827022018-09-25 18:49:23 +0200223 'KEY_TYPE': self.key_types,
224 'KEY_USAGE': self.key_usage_flags,
225 }
Gilles Peskine8c8694c2019-11-21 19:22:45 +0100226 # Test functions
227 self.table_by_test_function = {
Gilles Peskine8fa13482019-11-25 17:10:12 +0100228 # Any function ending in _algorithm also gets added to
229 # self.algorithms.
230 'key_type': [self.key_types],
Gilles Peskinef8210f22019-12-02 17:26:44 +0100231 'block_cipher_key_type': [self.key_types],
232 'stream_cipher_key_type': [self.key_types],
Gilles Peskine228abc52019-12-03 17:24:19 +0100233 'ecc_key_family': [self.ecc_curves],
Gilles Peskine8fa13482019-11-25 17:10:12 +0100234 'ecc_key_types': [self.ecc_curves],
Gilles Peskine228abc52019-12-03 17:24:19 +0100235 'dh_key_family': [self.dh_groups],
Gilles Peskine8fa13482019-11-25 17:10:12 +0100236 'dh_key_types': [self.dh_groups],
237 'hash_algorithm': [self.hash_algorithms],
238 'mac_algorithm': [self.mac_algorithms],
239 'cipher_algorithm': [],
240 'hmac_algorithm': [self.mac_algorithms],
241 'aead_algorithm': [self.aead_algorithms],
242 'key_derivation_algorithm': [self.kdf_algorithms],
243 'key_agreement_algorithm': [self.ka_algorithms],
244 'asymmetric_signature_algorithm': [],
245 'asymmetric_signature_wildcard': [self.algorithms],
246 'asymmetric_encryption_algorithm': [],
247 'other_algorithm': [],
Gilles Peskine8c8694c2019-11-21 19:22:45 +0100248 }
Gilles Peskine6f7ba5f2021-03-10 00:50:18 +0100249 self.arguments_for['mac_length'] += ['1', '63']
250 self.arguments_for['min_mac_length'] += ['1', '63']
251 self.arguments_for['tag_length'] += ['1', '63']
252 self.arguments_for['min_tag_length'] += ['1', '63']
Gilles Peskine24827022018-09-25 18:49:23 +0200253
Gilles Peskineffe2d6e2019-11-21 17:17:01 +0100254 def get_names(self, type_word):
255 """Return the set of known names of values of the given type."""
256 return {
257 'status': self.statuses,
258 'algorithm': self.algorithms,
259 'ecc_curve': self.ecc_curves,
260 'dh_group': self.dh_groups,
261 'key_type': self.key_types,
262 'key_usage': self.key_usage_flags,
263 }[type_word]
264
Gilles Peskine24827022018-09-25 18:49:23 +0200265 # Regex for interesting header lines.
266 # Groups: 1=macro name, 2=type, 3=argument list (optional).
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200267 _header_line_re = \
Gilles Peskine24827022018-09-25 18:49:23 +0200268 re.compile(r'#define +' +
Gilles Peskine98a710c2019-11-21 18:58:36 +0100269 r'(PSA_((?:(?:DH|ECC|KEY)_)?[A-Z]+)_\w+)' +
Gilles Peskine24827022018-09-25 18:49:23 +0200270 r'(?:\(([^\n()]*)\))?')
271 # Regex of macro names to exclude.
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200272 _excluded_name_re = re.compile(r'_(?:GET|IS|OF)_|_(?:BASE|FLAG|MASK)\Z')
Gilles Peskinec68ce962018-10-19 11:31:52 +0200273 # Additional excluded macros.
Gilles Peskine5c196fb2019-05-17 12:04:41 +0200274 _excluded_names = set([
275 # Macros that provide an alternative way to build the same
276 # algorithm as another macro.
Bence Szépkútia63b20d2020-12-16 11:36:46 +0100277 'PSA_ALG_AEAD_WITH_DEFAULT_LENGTH_TAG',
Gilles Peskine5c196fb2019-05-17 12:04:41 +0200278 'PSA_ALG_FULL_LENGTH_MAC',
279 # Auxiliary macro whose name doesn't fit the usual patterns for
280 # auxiliary macros.
Bence Szépkútia63b20d2020-12-16 11:36:46 +0100281 'PSA_ALG_AEAD_WITH_DEFAULT_LENGTH_TAG_CASE',
Gilles Peskine5c196fb2019-05-17 12:04:41 +0200282 ])
Gilles Peskine24827022018-09-25 18:49:23 +0200283 def parse_header_line(self, line):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200284 """Parse a C header line, looking for "#define PSA_xxx"."""
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200285 m = re.match(self._header_line_re, line)
Gilles Peskine24827022018-09-25 18:49:23 +0200286 if not m:
287 return
288 name = m.group(1)
Gilles Peskine2bcfc712019-11-21 19:49:26 +0100289 self.all_declared.add(name)
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200290 if re.search(self._excluded_name_re, name) or \
291 name in self._excluded_names:
Gilles Peskine24827022018-09-25 18:49:23 +0200292 return
293 dest = self.table_by_prefix.get(m.group(2))
294 if dest is None:
295 return
296 dest.add(name)
297 if m.group(3):
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200298 self.argspecs[name] = self._argument_split(m.group(3))
Gilles Peskine24827022018-09-25 18:49:23 +0200299
Gilles Peskine49af2d32019-12-06 19:20:13 +0100300 _nonascii_re = re.compile(rb'[^\x00-\x7f]+')
Gilles Peskine24827022018-09-25 18:49:23 +0200301 def parse_header(self, filename):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200302 """Parse a C header file, looking for "#define PSA_xxx"."""
Gilles Peskine49af2d32019-12-06 19:20:13 +0100303 with read_file_lines(filename, binary=True) as lines:
Gilles Peskinea0a315c2018-10-19 11:27:10 +0200304 for line in lines:
Gilles Peskine49af2d32019-12-06 19:20:13 +0100305 line = re.sub(self._nonascii_re, rb'', line).decode('ascii')
Gilles Peskine24827022018-09-25 18:49:23 +0200306 self.parse_header_line(line)
307
Gilles Peskine49af2d32019-12-06 19:20:13 +0100308 _macro_identifier_re = re.compile(r'[A-Z]\w+')
Gilles Peskine2bcfc712019-11-21 19:49:26 +0100309 def generate_undeclared_names(self, expr):
310 for name in re.findall(self._macro_identifier_re, expr):
311 if name not in self.all_declared:
312 yield name
313
314 def accept_test_case_line(self, function, argument):
315 #pylint: disable=unused-argument
316 undeclared = list(self.generate_undeclared_names(argument))
317 if undeclared:
318 raise Exception('Undeclared names in test case', undeclared)
319 return True
320
Gilles Peskine24827022018-09-25 18:49:23 +0200321 def add_test_case_line(self, function, argument):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200322 """Parse a test case data line, looking for algorithm metadata tests."""
Gilles Peskine8c8694c2019-11-21 19:22:45 +0100323 sets = []
Gilles Peskine24827022018-09-25 18:49:23 +0200324 if function.endswith('_algorithm'):
Gilles Peskine8c8694c2019-11-21 19:22:45 +0100325 sets.append(self.algorithms)
Gilles Peskine79616682019-11-21 20:08:10 +0100326 if function == 'key_agreement_algorithm' and \
327 argument.startswith('PSA_ALG_KEY_AGREEMENT('):
328 # We only want *raw* key agreement algorithms as such, so
329 # exclude ones that are already chained with a KDF.
330 # Keep the expression as one to test as an algorithm.
331 function = 'other_algorithm'
Gilles Peskine8fa13482019-11-25 17:10:12 +0100332 sets += self.table_by_test_function[function]
Gilles Peskine2bcfc712019-11-21 19:49:26 +0100333 if self.accept_test_case_line(function, argument):
334 for s in sets:
335 s.add(argument)
Gilles Peskine24827022018-09-25 18:49:23 +0200336
337 # Regex matching a *.data line containing a test function call and
338 # its arguments. The actual definition is partly positional, but this
339 # regex is good enough in practice.
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200340 _test_case_line_re = re.compile(r'(?!depends_on:)(\w+):([^\n :][^:\n]*)')
Gilles Peskine24827022018-09-25 18:49:23 +0200341 def parse_test_cases(self, filename):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200342 """Parse a test case file (*.data), looking for algorithm metadata tests."""
Gilles Peskinea0a315c2018-10-19 11:27:10 +0200343 with read_file_lines(filename) as lines:
344 for line in lines:
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200345 m = re.match(self._test_case_line_re, line)
Gilles Peskine24827022018-09-25 18:49:23 +0200346 if m:
347 self.add_test_case_line(m.group(1), m.group(2))
348
Gilles Peskine6f7ba5f2021-03-10 00:50:18 +0100349def gather_inputs(headers, test_suites, inputs_class=InputsForTest):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200350 """Read the list of inputs to test psa_constant_names with."""
Gilles Peskine84a45812019-11-21 19:50:33 +0100351 inputs = inputs_class()
Gilles Peskine24827022018-09-25 18:49:23 +0200352 for header in headers:
353 inputs.parse_header(header)
354 for test_cases in test_suites:
355 inputs.parse_test_cases(test_cases)
356 inputs.gather_arguments()
357 return inputs
358
Gilles Peskineb86b6d32019-11-21 17:26:10 +0100359def run_c(type_word, expressions, include_path=None, keep_c=False):
Gilles Peskine2991b5f2021-01-19 21:19:02 +0100360 """Generate and run a program to print out numerical values of C expressions."""
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200361 if type_word == 'status':
Gilles Peskinec4cd2ad2019-02-13 18:42:53 +0100362 cast_to = 'long'
363 printf_format = '%ld'
364 else:
365 cast_to = 'unsigned long'
366 printf_format = '0x%08lx'
Gilles Peskine2adebc82020-12-11 00:30:53 +0100367 return c_build_helper.get_c_expression_values(
Gilles Peskinefc622112020-12-11 00:27:14 +0100368 cast_to, printf_format,
369 expressions,
370 caller='test_psa_constant_names.py for {} values'.format(type_word),
371 file_label=type_word,
372 header='#include <psa/crypto.h>',
373 include_path=include_path,
374 keep_c=keep_c
375 )
Gilles Peskine24827022018-09-25 18:49:23 +0200376
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200377NORMALIZE_STRIP_RE = re.compile(r'\s+')
Gilles Peskine24827022018-09-25 18:49:23 +0200378def normalize(expr):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200379 """Normalize the C expression so as not to care about trivial differences.
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100380
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200381 Currently "trivial differences" means whitespace.
382 """
Gilles Peskine5a6dc892019-11-21 16:48:07 +0100383 return re.sub(NORMALIZE_STRIP_RE, '', expr)
Gilles Peskine24827022018-09-25 18:49:23 +0200384
Gilles Peskineb86b6d32019-11-21 17:26:10 +0100385def collect_values(inputs, type_word, include_path=None, keep_c=False):
Gilles Peskinec2317112019-11-21 17:17:39 +0100386 """Generate expressions using known macro names and calculate their values.
387
388 Return a list of pairs of (expr, value) where expr is an expression and
389 value is a string representation of its integer value.
390 """
391 names = inputs.get_names(type_word)
392 expressions = sorted(inputs.generate_expressions(names))
Gilles Peskineb86b6d32019-11-21 17:26:10 +0100393 values = run_c(type_word, expressions,
394 include_path=include_path, keep_c=keep_c)
Gilles Peskinec2317112019-11-21 17:17:39 +0100395 return expressions, values
396
Gilles Peskine24609332019-11-21 17:44:21 +0100397class Tests:
398 """An object representing tests and their results."""
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100399
Gilles Peskinea5000f12019-11-21 17:51:11 +0100400 Error = namedtuple('Error',
401 ['type', 'expression', 'value', 'output'])
402
Gilles Peskine24609332019-11-21 17:44:21 +0100403 def __init__(self, options):
404 self.options = options
405 self.count = 0
406 self.errors = []
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100407
Gilles Peskine24609332019-11-21 17:44:21 +0100408 def run_one(self, inputs, type_word):
409 """Test psa_constant_names for the specified type.
Gilles Peskine24827022018-09-25 18:49:23 +0200410
Gilles Peskine24609332019-11-21 17:44:21 +0100411 Run the program on the names for this type.
412 Use the inputs to figure out what arguments to pass to macros that
413 take arguments.
414 """
415 expressions, values = collect_values(inputs, type_word,
416 include_path=self.options.include,
417 keep_c=self.options.keep_c)
418 output = subprocess.check_output([self.options.program, type_word] +
419 values)
420 outputs = output.decode('ascii').strip().split('\n')
421 self.count += len(expressions)
422 for expr, value, output in zip(expressions, values, outputs):
Gilles Peskine32558482019-12-03 19:03:35 +0100423 if self.options.show:
424 sys.stdout.write('{} {}\t{}\n'.format(type_word, value, output))
Gilles Peskine24609332019-11-21 17:44:21 +0100425 if normalize(expr) != normalize(output):
Gilles Peskinea5000f12019-11-21 17:51:11 +0100426 self.errors.append(self.Error(type=type_word,
427 expression=expr,
428 value=value,
429 output=output))
Gilles Peskine24827022018-09-25 18:49:23 +0200430
Gilles Peskine24609332019-11-21 17:44:21 +0100431 def run_all(self, inputs):
432 """Run psa_constant_names on all the gathered inputs."""
433 for type_word in ['status', 'algorithm', 'ecc_curve', 'dh_group',
434 'key_type', 'key_usage']:
435 self.run_one(inputs, type_word)
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100436
Gilles Peskine24609332019-11-21 17:44:21 +0100437 def report(self, out):
438 """Describe each case where the output is not as expected.
439
440 Write the errors to ``out``.
441 Also write a total.
442 """
Gilles Peskinea5000f12019-11-21 17:51:11 +0100443 for error in self.errors:
Gilles Peskine24609332019-11-21 17:44:21 +0100444 out.write('For {} "{}", got "{}" (value: {})\n'
Gilles Peskinea5000f12019-11-21 17:51:11 +0100445 .format(error.type, error.expression,
446 error.output, error.value))
Gilles Peskine24609332019-11-21 17:44:21 +0100447 out.write('{} test cases'.format(self.count))
448 if self.errors:
449 out.write(', {} FAIL\n'.format(len(self.errors)))
450 else:
451 out.write(' PASS\n')
Gilles Peskine24827022018-09-25 18:49:23 +0200452
Gilles Peskine69f93b52019-11-21 16:49:50 +0100453HEADERS = ['psa/crypto.h', 'psa/crypto_extra.h', 'psa/crypto_values.h']
454TEST_SUITES = ['tests/suites/test_suite_psa_crypto_metadata.data']
455
Gilles Peskine54f54452019-05-27 18:31:59 +0200456def main():
Gilles Peskine24827022018-09-25 18:49:23 +0200457 parser = argparse.ArgumentParser(description=globals()['__doc__'])
458 parser.add_argument('--include', '-I',
459 action='append', default=['include'],
460 help='Directory for header files')
Gilles Peskinecf9c18e2018-10-19 11:28:42 +0200461 parser.add_argument('--keep-c',
462 action='store_true', dest='keep_c', default=False,
463 help='Keep the intermediate C file')
464 parser.add_argument('--no-keep-c',
465 action='store_false', dest='keep_c',
466 help='Don\'t keep the intermediate C file (default)')
Gilles Peskine8f5a5012019-11-21 16:49:10 +0100467 parser.add_argument('--program',
468 default='programs/psa/psa_constant_names',
469 help='Program to test')
Gilles Peskine32558482019-12-03 19:03:35 +0100470 parser.add_argument('--show',
471 action='store_true',
472 help='Keep the intermediate C file')
473 parser.add_argument('--no-show',
474 action='store_false', dest='show',
475 help='Don\'t show tested values (default)')
Gilles Peskine24827022018-09-25 18:49:23 +0200476 options = parser.parse_args()
Gilles Peskine69f93b52019-11-21 16:49:50 +0100477 headers = [os.path.join(options.include[0], h) for h in HEADERS]
478 inputs = gather_inputs(headers, TEST_SUITES)
Gilles Peskine24609332019-11-21 17:44:21 +0100479 tests = Tests(options)
480 tests.run_all(inputs)
481 tests.report(sys.stdout)
482 if tests.errors:
Gilles Peskine8b022352020-03-24 18:36:56 +0100483 sys.exit(1)
Gilles Peskine54f54452019-05-27 18:31:59 +0200484
485if __name__ == '__main__':
486 main()