blob: 9e8d7f8f411370533f4972c99374c23052fde085 [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 Peskinea0a315c2018-10-19 11:27:10 +020036class ReadFileLineException(Exception):
37 def __init__(self, filename, line_number):
38 message = 'in {} at {}'.format(filename, line_number)
39 super(ReadFileLineException, self).__init__(message)
40 self.filename = filename
41 self.line_number = line_number
42
43class read_file_lines:
Gilles Peskine54f54452019-05-27 18:31:59 +020044 # Dear Pylint, conventionally, a context manager class name is lowercase.
45 # pylint: disable=invalid-name,too-few-public-methods
Gilles Peskinea3b93ff2019-06-03 11:23:56 +020046 """Context manager to read a text file line by line.
47
48 ```
49 with read_file_lines(filename) as lines:
50 for line in lines:
51 process(line)
52 ```
53 is equivalent to
54 ```
55 with open(filename, 'r') as input_file:
56 for line in input_file:
57 process(line)
58 ```
59 except that if process(line) raises an exception, then the read_file_lines
60 snippet annotates the exception with the file name and line number.
61 """
Gilles Peskine49af2d32019-12-06 19:20:13 +010062 def __init__(self, filename, binary=False):
Gilles Peskinea0a315c2018-10-19 11:27:10 +020063 self.filename = filename
64 self.line_number = 'entry'
Gilles Peskine54f54452019-05-27 18:31:59 +020065 self.generator = None
Gilles Peskine49af2d32019-12-06 19:20:13 +010066 self.binary = binary
Gilles Peskinea0a315c2018-10-19 11:27:10 +020067 def __enter__(self):
Gilles Peskine49af2d32019-12-06 19:20:13 +010068 self.generator = enumerate(open(self.filename,
69 'rb' if self.binary else 'r'))
Gilles Peskinea0a315c2018-10-19 11:27:10 +020070 return self
71 def __iter__(self):
72 for line_number, content in self.generator:
73 self.line_number = line_number
74 yield content
75 self.line_number = 'exit'
Gilles Peskine42a0a0a2019-05-27 18:29:47 +020076 def __exit__(self, exc_type, exc_value, exc_traceback):
77 if exc_type is not None:
Gilles Peskinea0a315c2018-10-19 11:27:10 +020078 raise ReadFileLineException(self.filename, self.line_number) \
Gilles Peskine42a0a0a2019-05-27 18:29:47 +020079 from exc_value
Gilles Peskinea0a315c2018-10-19 11:27:10 +020080
Gilles Peskine24827022018-09-25 18:49:23 +020081class Inputs:
Gilles Peskine8c8694c2019-11-21 19:22:45 +010082 # pylint: disable=too-many-instance-attributes
Gilles Peskinea3b93ff2019-06-03 11:23:56 +020083 """Accumulate information about macros to test.
Gilles Peskine4408dfd2019-11-21 17:16:21 +010084
Gilles Peskinea3b93ff2019-06-03 11:23:56 +020085 This includes macro names as well as information about their arguments
86 when applicable.
87 """
88
Gilles Peskine24827022018-09-25 18:49:23 +020089 def __init__(self):
Gilles Peskine2bcfc712019-11-21 19:49:26 +010090 self.all_declared = set()
Gilles Peskine24827022018-09-25 18:49:23 +020091 # Sets of names per type
92 self.statuses = set(['PSA_SUCCESS'])
93 self.algorithms = set(['0xffffffff'])
Gilles Peskinef65ed6f2019-12-04 17:18:41 +010094 self.ecc_curves = set(['0xff'])
95 self.dh_groups = set(['0xff'])
96 self.key_types = set(['0xffff'])
Gilles Peskine24827022018-09-25 18:49:23 +020097 self.key_usage_flags = set(['0x80000000'])
Bence Szépkúti4af65602020-12-08 11:10:21 +010098 # Hard-coded values for unknown algorithms
99 #
100 # These have to have values that are correct for their respective
101 # PSA_ALG_IS_xxx macros, but are also not currently assigned and are
102 # not likely to be assigned in the near future.
103 self.hash_algorithms = set(['0x020000fe']) # 0x020000ff is PSA_ALG_ANY_HASH
Steven Cooreman4400c3a2021-02-22 18:53:07 +0100104 self.mac_algorithms = set(['0x03007fff'])
Bence Szépkúti7e37bf92020-12-08 07:33:08 +0100105 self.ka_algorithms = set(['0x09fc0000'])
106 self.kdf_algorithms = set(['0x080000ff'])
Gilles Peskine434899f2018-10-19 11:30:26 +0200107 # For AEAD algorithms, the only variability is over the tag length,
108 # and this only applies to known algorithms, so don't test an
109 # unknown algorithm.
110 self.aead_algorithms = set()
Gilles Peskine24827022018-09-25 18:49:23 +0200111 # Identifier prefixes
112 self.table_by_prefix = {
113 'ERROR': self.statuses,
114 'ALG': self.algorithms,
Gilles Peskine98a710c2019-11-21 18:58:36 +0100115 'ECC_CURVE': self.ecc_curves,
116 'DH_GROUP': self.dh_groups,
Gilles Peskine24827022018-09-25 18:49:23 +0200117 'KEY_TYPE': self.key_types,
118 'KEY_USAGE': self.key_usage_flags,
119 }
Gilles Peskine8c8694c2019-11-21 19:22:45 +0100120 # Test functions
121 self.table_by_test_function = {
Gilles Peskine8fa13482019-11-25 17:10:12 +0100122 # Any function ending in _algorithm also gets added to
123 # self.algorithms.
124 'key_type': [self.key_types],
Gilles Peskinef8210f22019-12-02 17:26:44 +0100125 'block_cipher_key_type': [self.key_types],
126 'stream_cipher_key_type': [self.key_types],
Gilles Peskine228abc52019-12-03 17:24:19 +0100127 'ecc_key_family': [self.ecc_curves],
Gilles Peskine8fa13482019-11-25 17:10:12 +0100128 'ecc_key_types': [self.ecc_curves],
Gilles Peskine228abc52019-12-03 17:24:19 +0100129 'dh_key_family': [self.dh_groups],
Gilles Peskine8fa13482019-11-25 17:10:12 +0100130 'dh_key_types': [self.dh_groups],
131 'hash_algorithm': [self.hash_algorithms],
132 'mac_algorithm': [self.mac_algorithms],
133 'cipher_algorithm': [],
134 'hmac_algorithm': [self.mac_algorithms],
135 'aead_algorithm': [self.aead_algorithms],
136 'key_derivation_algorithm': [self.kdf_algorithms],
137 'key_agreement_algorithm': [self.ka_algorithms],
138 'asymmetric_signature_algorithm': [],
139 'asymmetric_signature_wildcard': [self.algorithms],
140 'asymmetric_encryption_algorithm': [],
141 'other_algorithm': [],
Gilles Peskine8c8694c2019-11-21 19:22:45 +0100142 }
Gilles Peskine24827022018-09-25 18:49:23 +0200143 # macro name -> list of argument names
144 self.argspecs = {}
145 # argument name -> list of values
Gilles Peskine434899f2018-10-19 11:30:26 +0200146 self.arguments_for = {
147 'mac_length': ['1', '63'],
148 'tag_length': ['1', '63'],
Steven Cooreman4400c3a2021-02-22 18:53:07 +0100149 'min_mac_length': ['1', '63'],
150 'min_tag_length': ['1', '63'],
Gilles Peskine434899f2018-10-19 11:30:26 +0200151 }
Gilles Peskine24827022018-09-25 18:49:23 +0200152
Gilles Peskineffe2d6e2019-11-21 17:17:01 +0100153 def get_names(self, type_word):
154 """Return the set of known names of values of the given type."""
155 return {
156 'status': self.statuses,
157 'algorithm': self.algorithms,
158 'ecc_curve': self.ecc_curves,
159 'dh_group': self.dh_groups,
160 'key_type': self.key_types,
161 'key_usage': self.key_usage_flags,
162 }[type_word]
163
Gilles Peskine24827022018-09-25 18:49:23 +0200164 def gather_arguments(self):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200165 """Populate the list of values for macro arguments.
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100166
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200167 Call this after parsing all the inputs.
168 """
Gilles Peskine24827022018-09-25 18:49:23 +0200169 self.arguments_for['hash_alg'] = sorted(self.hash_algorithms)
Gilles Peskine434899f2018-10-19 11:30:26 +0200170 self.arguments_for['mac_alg'] = sorted(self.mac_algorithms)
Gilles Peskine882e57e2019-04-12 00:12:07 +0200171 self.arguments_for['ka_alg'] = sorted(self.ka_algorithms)
Gilles Peskine17542082019-01-04 19:46:31 +0100172 self.arguments_for['kdf_alg'] = sorted(self.kdf_algorithms)
Gilles Peskine434899f2018-10-19 11:30:26 +0200173 self.arguments_for['aead_alg'] = sorted(self.aead_algorithms)
Gilles Peskine24827022018-09-25 18:49:23 +0200174 self.arguments_for['curve'] = sorted(self.ecc_curves)
Gilles Peskinedcaefae2019-05-16 12:55:35 +0200175 self.arguments_for['group'] = sorted(self.dh_groups)
Gilles Peskine24827022018-09-25 18:49:23 +0200176
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200177 @staticmethod
178 def _format_arguments(name, arguments):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200179 """Format a macro call with arguments.."""
Gilles Peskine24827022018-09-25 18:49:23 +0200180 return name + '(' + ', '.join(arguments) + ')'
181
182 def distribute_arguments(self, name):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200183 """Generate macro calls with each tested argument set.
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100184
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200185 If name is a macro without arguments, just yield "name".
186 If name is a macro with arguments, yield a series of
187 "name(arg1,...,argN)" where each argument takes each possible
188 value at least once.
189 """
Gilles Peskinea0a315c2018-10-19 11:27:10 +0200190 try:
191 if name not in self.argspecs:
192 yield name
193 return
194 argspec = self.argspecs[name]
195 if argspec == []:
196 yield name + '()'
197 return
198 argument_lists = [self.arguments_for[arg] for arg in argspec]
199 arguments = [values[0] for values in argument_lists]
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200200 yield self._format_arguments(name, arguments)
Gilles Peskine54f54452019-05-27 18:31:59 +0200201 # Dear Pylint, enumerate won't work here since we're modifying
202 # the array.
203 # pylint: disable=consider-using-enumerate
Gilles Peskinea0a315c2018-10-19 11:27:10 +0200204 for i in range(len(arguments)):
205 for value in argument_lists[i][1:]:
206 arguments[i] = value
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200207 yield self._format_arguments(name, arguments)
Gilles Peskinef96ed662018-10-19 11:29:56 +0200208 arguments[i] = argument_lists[0][0]
Gilles Peskinea0a315c2018-10-19 11:27:10 +0200209 except BaseException as e:
210 raise Exception('distribute_arguments({})'.format(name)) from e
Gilles Peskine24827022018-09-25 18:49:23 +0200211
Gilles Peskine5a994c12019-11-21 16:46:51 +0100212 def generate_expressions(self, names):
213 return itertools.chain(*map(self.distribute_arguments, names))
214
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200215 _argument_split_re = re.compile(r' *, *')
216 @classmethod
217 def _argument_split(cls, arguments):
218 return re.split(cls._argument_split_re, arguments)
219
Gilles Peskine24827022018-09-25 18:49:23 +0200220 # Regex for interesting header lines.
221 # Groups: 1=macro name, 2=type, 3=argument list (optional).
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200222 _header_line_re = \
Gilles Peskine24827022018-09-25 18:49:23 +0200223 re.compile(r'#define +' +
Gilles Peskine98a710c2019-11-21 18:58:36 +0100224 r'(PSA_((?:(?:DH|ECC|KEY)_)?[A-Z]+)_\w+)' +
Gilles Peskine24827022018-09-25 18:49:23 +0200225 r'(?:\(([^\n()]*)\))?')
226 # Regex of macro names to exclude.
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200227 _excluded_name_re = re.compile(r'_(?:GET|IS|OF)_|_(?:BASE|FLAG|MASK)\Z')
Gilles Peskinec68ce962018-10-19 11:31:52 +0200228 # Additional excluded macros.
Gilles Peskine5c196fb2019-05-17 12:04:41 +0200229 _excluded_names = set([
230 # Macros that provide an alternative way to build the same
231 # algorithm as another macro.
Bence Szépkútia63b20d2020-12-16 11:36:46 +0100232 'PSA_ALG_AEAD_WITH_DEFAULT_LENGTH_TAG',
Gilles Peskine5c196fb2019-05-17 12:04:41 +0200233 'PSA_ALG_FULL_LENGTH_MAC',
234 # Auxiliary macro whose name doesn't fit the usual patterns for
235 # auxiliary macros.
Bence Szépkútia63b20d2020-12-16 11:36:46 +0100236 'PSA_ALG_AEAD_WITH_DEFAULT_LENGTH_TAG_CASE',
Gilles Peskine5c196fb2019-05-17 12:04:41 +0200237 ])
Gilles Peskine24827022018-09-25 18:49:23 +0200238 def parse_header_line(self, line):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200239 """Parse a C header line, looking for "#define PSA_xxx"."""
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200240 m = re.match(self._header_line_re, line)
Gilles Peskine24827022018-09-25 18:49:23 +0200241 if not m:
242 return
243 name = m.group(1)
Gilles Peskine2bcfc712019-11-21 19:49:26 +0100244 self.all_declared.add(name)
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200245 if re.search(self._excluded_name_re, name) or \
246 name in self._excluded_names:
Gilles Peskine24827022018-09-25 18:49:23 +0200247 return
248 dest = self.table_by_prefix.get(m.group(2))
249 if dest is None:
250 return
251 dest.add(name)
252 if m.group(3):
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200253 self.argspecs[name] = self._argument_split(m.group(3))
Gilles Peskine24827022018-09-25 18:49:23 +0200254
Gilles Peskine49af2d32019-12-06 19:20:13 +0100255 _nonascii_re = re.compile(rb'[^\x00-\x7f]+')
Gilles Peskine24827022018-09-25 18:49:23 +0200256 def parse_header(self, filename):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200257 """Parse a C header file, looking for "#define PSA_xxx"."""
Gilles Peskine49af2d32019-12-06 19:20:13 +0100258 with read_file_lines(filename, binary=True) as lines:
Gilles Peskinea0a315c2018-10-19 11:27:10 +0200259 for line in lines:
Gilles Peskine49af2d32019-12-06 19:20:13 +0100260 line = re.sub(self._nonascii_re, rb'', line).decode('ascii')
Gilles Peskine24827022018-09-25 18:49:23 +0200261 self.parse_header_line(line)
262
Gilles Peskine49af2d32019-12-06 19:20:13 +0100263 _macro_identifier_re = re.compile(r'[A-Z]\w+')
Gilles Peskine2bcfc712019-11-21 19:49:26 +0100264 def generate_undeclared_names(self, expr):
265 for name in re.findall(self._macro_identifier_re, expr):
266 if name not in self.all_declared:
267 yield name
268
269 def accept_test_case_line(self, function, argument):
270 #pylint: disable=unused-argument
271 undeclared = list(self.generate_undeclared_names(argument))
272 if undeclared:
273 raise Exception('Undeclared names in test case', undeclared)
274 return True
275
Gilles Peskine24827022018-09-25 18:49:23 +0200276 def add_test_case_line(self, function, argument):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200277 """Parse a test case data line, looking for algorithm metadata tests."""
Gilles Peskine8c8694c2019-11-21 19:22:45 +0100278 sets = []
Gilles Peskine24827022018-09-25 18:49:23 +0200279 if function.endswith('_algorithm'):
Gilles Peskine8c8694c2019-11-21 19:22:45 +0100280 sets.append(self.algorithms)
Gilles Peskine79616682019-11-21 20:08:10 +0100281 if function == 'key_agreement_algorithm' and \
282 argument.startswith('PSA_ALG_KEY_AGREEMENT('):
283 # We only want *raw* key agreement algorithms as such, so
284 # exclude ones that are already chained with a KDF.
285 # Keep the expression as one to test as an algorithm.
286 function = 'other_algorithm'
Gilles Peskine8fa13482019-11-25 17:10:12 +0100287 sets += self.table_by_test_function[function]
Gilles Peskine2bcfc712019-11-21 19:49:26 +0100288 if self.accept_test_case_line(function, argument):
289 for s in sets:
290 s.add(argument)
Gilles Peskine24827022018-09-25 18:49:23 +0200291
292 # Regex matching a *.data line containing a test function call and
293 # its arguments. The actual definition is partly positional, but this
294 # regex is good enough in practice.
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200295 _test_case_line_re = re.compile(r'(?!depends_on:)(\w+):([^\n :][^:\n]*)')
Gilles Peskine24827022018-09-25 18:49:23 +0200296 def parse_test_cases(self, filename):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200297 """Parse a test case file (*.data), looking for algorithm metadata tests."""
Gilles Peskinea0a315c2018-10-19 11:27:10 +0200298 with read_file_lines(filename) as lines:
299 for line in lines:
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200300 m = re.match(self._test_case_line_re, line)
Gilles Peskine24827022018-09-25 18:49:23 +0200301 if m:
302 self.add_test_case_line(m.group(1), m.group(2))
303
Gilles Peskine84a45812019-11-21 19:50:33 +0100304def gather_inputs(headers, test_suites, inputs_class=Inputs):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200305 """Read the list of inputs to test psa_constant_names with."""
Gilles Peskine84a45812019-11-21 19:50:33 +0100306 inputs = inputs_class()
Gilles Peskine24827022018-09-25 18:49:23 +0200307 for header in headers:
308 inputs.parse_header(header)
309 for test_cases in test_suites:
310 inputs.parse_test_cases(test_cases)
311 inputs.gather_arguments()
312 return inputs
313
Gilles Peskineb86b6d32019-11-21 17:26:10 +0100314def run_c(type_word, expressions, include_path=None, keep_c=False):
Gilles Peskine2991b5f2021-01-19 21:19:02 +0100315 """Generate and run a program to print out numerical values of C expressions."""
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200316 if type_word == 'status':
Gilles Peskinec4cd2ad2019-02-13 18:42:53 +0100317 cast_to = 'long'
318 printf_format = '%ld'
319 else:
320 cast_to = 'unsigned long'
321 printf_format = '0x%08lx'
Gilles Peskine2adebc82020-12-11 00:30:53 +0100322 return c_build_helper.get_c_expression_values(
Gilles Peskinefc622112020-12-11 00:27:14 +0100323 cast_to, printf_format,
324 expressions,
325 caller='test_psa_constant_names.py for {} values'.format(type_word),
326 file_label=type_word,
327 header='#include <psa/crypto.h>',
328 include_path=include_path,
329 keep_c=keep_c
330 )
Gilles Peskine24827022018-09-25 18:49:23 +0200331
Gilles Peskine42a0a0a2019-05-27 18:29:47 +0200332NORMALIZE_STRIP_RE = re.compile(r'\s+')
Gilles Peskine24827022018-09-25 18:49:23 +0200333def normalize(expr):
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200334 """Normalize the C expression so as not to care about trivial differences.
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100335
Gilles Peskinea3b93ff2019-06-03 11:23:56 +0200336 Currently "trivial differences" means whitespace.
337 """
Gilles Peskine5a6dc892019-11-21 16:48:07 +0100338 return re.sub(NORMALIZE_STRIP_RE, '', expr)
Gilles Peskine24827022018-09-25 18:49:23 +0200339
Gilles Peskineb86b6d32019-11-21 17:26:10 +0100340def collect_values(inputs, type_word, include_path=None, keep_c=False):
Gilles Peskinec2317112019-11-21 17:17:39 +0100341 """Generate expressions using known macro names and calculate their values.
342
343 Return a list of pairs of (expr, value) where expr is an expression and
344 value is a string representation of its integer value.
345 """
346 names = inputs.get_names(type_word)
347 expressions = sorted(inputs.generate_expressions(names))
Gilles Peskineb86b6d32019-11-21 17:26:10 +0100348 values = run_c(type_word, expressions,
349 include_path=include_path, keep_c=keep_c)
Gilles Peskinec2317112019-11-21 17:17:39 +0100350 return expressions, values
351
Gilles Peskine24609332019-11-21 17:44:21 +0100352class Tests:
353 """An object representing tests and their results."""
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100354
Gilles Peskinea5000f12019-11-21 17:51:11 +0100355 Error = namedtuple('Error',
356 ['type', 'expression', 'value', 'output'])
357
Gilles Peskine24609332019-11-21 17:44:21 +0100358 def __init__(self, options):
359 self.options = options
360 self.count = 0
361 self.errors = []
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100362
Gilles Peskine24609332019-11-21 17:44:21 +0100363 def run_one(self, inputs, type_word):
364 """Test psa_constant_names for the specified type.
Gilles Peskine24827022018-09-25 18:49:23 +0200365
Gilles Peskine24609332019-11-21 17:44:21 +0100366 Run the program on the names for this type.
367 Use the inputs to figure out what arguments to pass to macros that
368 take arguments.
369 """
370 expressions, values = collect_values(inputs, type_word,
371 include_path=self.options.include,
372 keep_c=self.options.keep_c)
373 output = subprocess.check_output([self.options.program, type_word] +
374 values)
375 outputs = output.decode('ascii').strip().split('\n')
376 self.count += len(expressions)
377 for expr, value, output in zip(expressions, values, outputs):
Gilles Peskine32558482019-12-03 19:03:35 +0100378 if self.options.show:
379 sys.stdout.write('{} {}\t{}\n'.format(type_word, value, output))
Gilles Peskine24609332019-11-21 17:44:21 +0100380 if normalize(expr) != normalize(output):
Gilles Peskinea5000f12019-11-21 17:51:11 +0100381 self.errors.append(self.Error(type=type_word,
382 expression=expr,
383 value=value,
384 output=output))
Gilles Peskine24827022018-09-25 18:49:23 +0200385
Gilles Peskine24609332019-11-21 17:44:21 +0100386 def run_all(self, inputs):
387 """Run psa_constant_names on all the gathered inputs."""
388 for type_word in ['status', 'algorithm', 'ecc_curve', 'dh_group',
389 'key_type', 'key_usage']:
390 self.run_one(inputs, type_word)
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100391
Gilles Peskine24609332019-11-21 17:44:21 +0100392 def report(self, out):
393 """Describe each case where the output is not as expected.
394
395 Write the errors to ``out``.
396 Also write a total.
397 """
Gilles Peskinea5000f12019-11-21 17:51:11 +0100398 for error in self.errors:
Gilles Peskine24609332019-11-21 17:44:21 +0100399 out.write('For {} "{}", got "{}" (value: {})\n'
Gilles Peskinea5000f12019-11-21 17:51:11 +0100400 .format(error.type, error.expression,
401 error.output, error.value))
Gilles Peskine24609332019-11-21 17:44:21 +0100402 out.write('{} test cases'.format(self.count))
403 if self.errors:
404 out.write(', {} FAIL\n'.format(len(self.errors)))
405 else:
406 out.write(' PASS\n')
Gilles Peskine24827022018-09-25 18:49:23 +0200407
Gilles Peskine69f93b52019-11-21 16:49:50 +0100408HEADERS = ['psa/crypto.h', 'psa/crypto_extra.h', 'psa/crypto_values.h']
409TEST_SUITES = ['tests/suites/test_suite_psa_crypto_metadata.data']
410
Gilles Peskine54f54452019-05-27 18:31:59 +0200411def main():
Gilles Peskine24827022018-09-25 18:49:23 +0200412 parser = argparse.ArgumentParser(description=globals()['__doc__'])
413 parser.add_argument('--include', '-I',
414 action='append', default=['include'],
415 help='Directory for header files')
Gilles Peskinecf9c18e2018-10-19 11:28:42 +0200416 parser.add_argument('--keep-c',
417 action='store_true', dest='keep_c', default=False,
418 help='Keep the intermediate C file')
419 parser.add_argument('--no-keep-c',
420 action='store_false', dest='keep_c',
421 help='Don\'t keep the intermediate C file (default)')
Gilles Peskine8f5a5012019-11-21 16:49:10 +0100422 parser.add_argument('--program',
423 default='programs/psa/psa_constant_names',
424 help='Program to test')
Gilles Peskine32558482019-12-03 19:03:35 +0100425 parser.add_argument('--show',
426 action='store_true',
427 help='Keep the intermediate C file')
428 parser.add_argument('--no-show',
429 action='store_false', dest='show',
430 help='Don\'t show tested values (default)')
Gilles Peskine24827022018-09-25 18:49:23 +0200431 options = parser.parse_args()
Gilles Peskine69f93b52019-11-21 16:49:50 +0100432 headers = [os.path.join(options.include[0], h) for h in HEADERS]
433 inputs = gather_inputs(headers, TEST_SUITES)
Gilles Peskine24609332019-11-21 17:44:21 +0100434 tests = Tests(options)
435 tests.run_all(inputs)
436 tests.report(sys.stdout)
437 if tests.errors:
Gilles Peskine8b022352020-03-24 18:36:56 +0100438 sys.exit(1)
Gilles Peskine54f54452019-05-27 18:31:59 +0200439
440if __name__ == '__main__':
441 main()