blob: 6883e279faa772ef756cba3872ef118996670476 [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
Dave Rodgman7ff79652023-11-03 12:04:52 +000011# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
Bence Szépkúti700ee442020-05-26 00:33:31 +020012
Gilles Peskine24827022018-09-25 18:49:23 +020013import argparse
Gilles Peskinea5000f12019-11-21 17:51:11 +010014from collections import namedtuple
Gilles Peskine24827022018-09-25 18:49:23 +020015import os
Gilles Peskine24827022018-09-25 18:49:23 +020016import re
17import subprocess
18import sys
Gilles Peskine3cf3a8e2021-03-30 19:09:05 +020019from typing import Iterable, List, Optional, Tuple
Gilles Peskine2adebc82020-12-11 00:30:53 +010020
21import scripts_path # pylint: disable=unused-import
22from mbedtls_dev import c_build_helper
Gilles Peskine3cf3a8e2021-03-30 19:09:05 +020023from mbedtls_dev.macro_collector import InputsForTest, PSAMacroEnumerator
Gilles Peskinee30ad462021-03-29 20:37:40 +020024from mbedtls_dev import typing_util
Gilles Peskine6f7ba5f2021-03-10 00:50:18 +010025
Gilles Peskinee30ad462021-03-29 20:37:40 +020026def gather_inputs(headers: Iterable[str],
27 test_suites: Iterable[str],
28 inputs_class=InputsForTest) -> PSAMacroEnumerator:
Gilles Peskinea3b93ff2019-06-03 11:23:56 +020029 """Read the list of inputs to test psa_constant_names with."""
Gilles Peskine84a45812019-11-21 19:50:33 +010030 inputs = inputs_class()
Gilles Peskine24827022018-09-25 18:49:23 +020031 for header in headers:
32 inputs.parse_header(header)
33 for test_cases in test_suites:
34 inputs.parse_test_cases(test_cases)
Gilles Peskined6d2d6a2021-03-30 21:46:35 +020035 inputs.add_numerical_values()
Gilles Peskine24827022018-09-25 18:49:23 +020036 inputs.gather_arguments()
37 return inputs
38
Gilles Peskinee30ad462021-03-29 20:37:40 +020039def run_c(type_word: str,
40 expressions: Iterable[str],
41 include_path: Optional[str] = None,
42 keep_c: bool = False) -> List[str]:
Gilles Peskine2991b5f2021-01-19 21:19:02 +010043 """Generate and run a program to print out numerical values of C expressions."""
Gilles Peskine42a0a0a2019-05-27 18:29:47 +020044 if type_word == 'status':
Gilles Peskinec4cd2ad2019-02-13 18:42:53 +010045 cast_to = 'long'
46 printf_format = '%ld'
47 else:
48 cast_to = 'unsigned long'
49 printf_format = '0x%08lx'
Gilles Peskine2adebc82020-12-11 00:30:53 +010050 return c_build_helper.get_c_expression_values(
Gilles Peskinefc622112020-12-11 00:27:14 +010051 cast_to, printf_format,
52 expressions,
53 caller='test_psa_constant_names.py for {} values'.format(type_word),
54 file_label=type_word,
55 header='#include <psa/crypto.h>',
56 include_path=include_path,
57 keep_c=keep_c
58 )
Gilles Peskine24827022018-09-25 18:49:23 +020059
Gilles Peskine42a0a0a2019-05-27 18:29:47 +020060NORMALIZE_STRIP_RE = re.compile(r'\s+')
Gilles Peskinee30ad462021-03-29 20:37:40 +020061def normalize(expr: str) -> str:
Gilles Peskinea3b93ff2019-06-03 11:23:56 +020062 """Normalize the C expression so as not to care about trivial differences.
Gilles Peskine4408dfd2019-11-21 17:16:21 +010063
Gilles Peskinea3b93ff2019-06-03 11:23:56 +020064 Currently "trivial differences" means whitespace.
65 """
Gilles Peskine5a6dc892019-11-21 16:48:07 +010066 return re.sub(NORMALIZE_STRIP_RE, '', expr)
Gilles Peskine24827022018-09-25 18:49:23 +020067
Gilles Peskine041388a2022-03-19 18:06:52 +010068ALG_TRUNCATED_TO_SELF_RE = \
69 re.compile(r'PSA_ALG_AEAD_WITH_SHORTENED_TAG\('
70 r'PSA_ALG_(?:CCM|CHACHA20_POLY1305|GCM)'
71 r', *16\)\Z')
72
73def is_simplifiable(expr: str) -> bool:
74 """Determine whether an expression is simplifiable.
75
76 Simplifiable expressions can't be output in their input form, since
77 the output will be the simple form. Therefore they must be excluded
78 from testing.
79 """
80 if ALG_TRUNCATED_TO_SELF_RE.match(expr):
81 return True
82 return False
83
Gilles Peskinee30ad462021-03-29 20:37:40 +020084def collect_values(inputs: InputsForTest,
85 type_word: str,
86 include_path: Optional[str] = None,
87 keep_c: bool = False) -> Tuple[List[str], List[str]]:
Gilles Peskinec2317112019-11-21 17:17:39 +010088 """Generate expressions using known macro names and calculate their values.
89
90 Return a list of pairs of (expr, value) where expr is an expression and
91 value is a string representation of its integer value.
92 """
93 names = inputs.get_names(type_word)
Gilles Peskine041388a2022-03-19 18:06:52 +010094 expressions = sorted(expr
95 for expr in inputs.generate_expressions(names)
96 if not is_simplifiable(expr))
Gilles Peskineb86b6d32019-11-21 17:26:10 +010097 values = run_c(type_word, expressions,
98 include_path=include_path, keep_c=keep_c)
Gilles Peskinec2317112019-11-21 17:17:39 +010099 return expressions, values
100
Gilles Peskine24609332019-11-21 17:44:21 +0100101class Tests:
102 """An object representing tests and their results."""
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100103
Gilles Peskinea5000f12019-11-21 17:51:11 +0100104 Error = namedtuple('Error',
105 ['type', 'expression', 'value', 'output'])
106
Gilles Peskinee30ad462021-03-29 20:37:40 +0200107 def __init__(self, options) -> None:
Gilles Peskine24609332019-11-21 17:44:21 +0100108 self.options = options
109 self.count = 0
Gilles Peskinee30ad462021-03-29 20:37:40 +0200110 self.errors = [] #type: List[Tests.Error]
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100111
Gilles Peskinee30ad462021-03-29 20:37:40 +0200112 def run_one(self, inputs: InputsForTest, type_word: str) -> None:
Gilles Peskine24609332019-11-21 17:44:21 +0100113 """Test psa_constant_names for the specified type.
Gilles Peskine24827022018-09-25 18:49:23 +0200114
Gilles Peskine24609332019-11-21 17:44:21 +0100115 Run the program on the names for this type.
116 Use the inputs to figure out what arguments to pass to macros that
117 take arguments.
118 """
119 expressions, values = collect_values(inputs, type_word,
120 include_path=self.options.include,
121 keep_c=self.options.keep_c)
Gilles Peskinee30ad462021-03-29 20:37:40 +0200122 output_bytes = subprocess.check_output([self.options.program,
123 type_word] + values)
124 output = output_bytes.decode('ascii')
125 outputs = output.strip().split('\n')
Gilles Peskine24609332019-11-21 17:44:21 +0100126 self.count += len(expressions)
127 for expr, value, output in zip(expressions, values, outputs):
Gilles Peskine32558482019-12-03 19:03:35 +0100128 if self.options.show:
129 sys.stdout.write('{} {}\t{}\n'.format(type_word, value, output))
Gilles Peskine24609332019-11-21 17:44:21 +0100130 if normalize(expr) != normalize(output):
Gilles Peskinea5000f12019-11-21 17:51:11 +0100131 self.errors.append(self.Error(type=type_word,
132 expression=expr,
133 value=value,
134 output=output))
Gilles Peskine24827022018-09-25 18:49:23 +0200135
Gilles Peskinee30ad462021-03-29 20:37:40 +0200136 def run_all(self, inputs: InputsForTest) -> None:
Gilles Peskine24609332019-11-21 17:44:21 +0100137 """Run psa_constant_names on all the gathered inputs."""
138 for type_word in ['status', 'algorithm', 'ecc_curve', 'dh_group',
139 'key_type', 'key_usage']:
140 self.run_one(inputs, type_word)
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100141
Gilles Peskinee30ad462021-03-29 20:37:40 +0200142 def report(self, out: typing_util.Writable) -> None:
Gilles Peskine24609332019-11-21 17:44:21 +0100143 """Describe each case where the output is not as expected.
144
145 Write the errors to ``out``.
146 Also write a total.
147 """
Gilles Peskinea5000f12019-11-21 17:51:11 +0100148 for error in self.errors:
Gilles Peskine24609332019-11-21 17:44:21 +0100149 out.write('For {} "{}", got "{}" (value: {})\n'
Gilles Peskinea5000f12019-11-21 17:51:11 +0100150 .format(error.type, error.expression,
151 error.output, error.value))
Gilles Peskine24609332019-11-21 17:44:21 +0100152 out.write('{} test cases'.format(self.count))
153 if self.errors:
154 out.write(', {} FAIL\n'.format(len(self.errors)))
155 else:
156 out.write(' PASS\n')
Gilles Peskine24827022018-09-25 18:49:23 +0200157
Gilles Peskine69f93b52019-11-21 16:49:50 +0100158HEADERS = ['psa/crypto.h', 'psa/crypto_extra.h', 'psa/crypto_values.h']
159TEST_SUITES = ['tests/suites/test_suite_psa_crypto_metadata.data']
160
Gilles Peskine54f54452019-05-27 18:31:59 +0200161def main():
Gilles Peskine24827022018-09-25 18:49:23 +0200162 parser = argparse.ArgumentParser(description=globals()['__doc__'])
163 parser.add_argument('--include', '-I',
164 action='append', default=['include'],
165 help='Directory for header files')
Gilles Peskinecf9c18e2018-10-19 11:28:42 +0200166 parser.add_argument('--keep-c',
167 action='store_true', dest='keep_c', default=False,
168 help='Keep the intermediate C file')
169 parser.add_argument('--no-keep-c',
170 action='store_false', dest='keep_c',
171 help='Don\'t keep the intermediate C file (default)')
Gilles Peskine8f5a5012019-11-21 16:49:10 +0100172 parser.add_argument('--program',
173 default='programs/psa/psa_constant_names',
174 help='Program to test')
Gilles Peskine32558482019-12-03 19:03:35 +0100175 parser.add_argument('--show',
176 action='store_true',
Gilles Peskine4d59f012021-03-17 13:45:32 +0100177 help='Show tested values on stdout')
Gilles Peskine32558482019-12-03 19:03:35 +0100178 parser.add_argument('--no-show',
179 action='store_false', dest='show',
180 help='Don\'t show tested values (default)')
Gilles Peskine24827022018-09-25 18:49:23 +0200181 options = parser.parse_args()
Gilles Peskine69f93b52019-11-21 16:49:50 +0100182 headers = [os.path.join(options.include[0], h) for h in HEADERS]
183 inputs = gather_inputs(headers, TEST_SUITES)
Gilles Peskine24609332019-11-21 17:44:21 +0100184 tests = Tests(options)
185 tests.run_all(inputs)
186 tests.report(sys.stdout)
187 if tests.errors:
Gilles Peskine8b022352020-03-24 18:36:56 +0100188 sys.exit(1)
Gilles Peskine54f54452019-05-27 18:31:59 +0200189
190if __name__ == '__main__':
191 main()