blob: f35351c0793bee27427150b37f7db370186e6b00 [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 Rodgman16799db2023-11-02 19:47:20 +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 Peskineb4edff92021-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
David Horstmanncd84bb22024-05-03 14:36:12 +010022from mbedtls_framework import c_build_helper
23from mbedtls_framework.macro_collector import InputsForTest, PSAMacroEnumerator
24from mbedtls_framework import typing_util
Gilles Peskine6f7ba5f2021-03-10 00:50:18 +010025
Gilles Peskine95649ed2021-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 Peskine3d404b82021-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 Peskine95649ed2021-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 Peskine95649ed2021-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 Peskine17e350b2022-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 Peskine95649ed2021-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 Peskine17e350b2022-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 Peskine95649ed2021-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 Peskine95649ed2021-03-29 20:37:40 +0200110 self.errors = [] #type: List[Tests.Error]
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100111
Gilles Peskine95649ed2021-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 Peskine95649ed2021-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 Peskine95649ed2021-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 Peskine95649ed2021-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',
Ronald Cronf4606d42024-06-17 17:49:28 +0200164 action='append', default=['tf-psa-crypto/include',
165 'tf-psa-crypto/drivers/builtin/include',
166 'include'],
Gilles Peskine24827022018-09-25 18:49:23 +0200167 help='Directory for header files')
Gilles Peskinecf9c18e2018-10-19 11:28:42 +0200168 parser.add_argument('--keep-c',
169 action='store_true', dest='keep_c', default=False,
170 help='Keep the intermediate C file')
171 parser.add_argument('--no-keep-c',
172 action='store_false', dest='keep_c',
173 help='Don\'t keep the intermediate C file (default)')
Gilles Peskine8f5a5012019-11-21 16:49:10 +0100174 parser.add_argument('--program',
175 default='programs/psa/psa_constant_names',
176 help='Program to test')
Gilles Peskine32558482019-12-03 19:03:35 +0100177 parser.add_argument('--show',
178 action='store_true',
Gilles Peskinec893a572021-03-17 13:45:32 +0100179 help='Show tested values on stdout')
Gilles Peskine32558482019-12-03 19:03:35 +0100180 parser.add_argument('--no-show',
181 action='store_false', dest='show',
182 help='Don\'t show tested values (default)')
Gilles Peskine24827022018-09-25 18:49:23 +0200183 options = parser.parse_args()
Gilles Peskine69f93b52019-11-21 16:49:50 +0100184 headers = [os.path.join(options.include[0], h) for h in HEADERS]
185 inputs = gather_inputs(headers, TEST_SUITES)
Gilles Peskine24609332019-11-21 17:44:21 +0100186 tests = Tests(options)
187 tests.run_all(inputs)
188 tests.report(sys.stdout)
189 if tests.errors:
Gilles Peskine8b022352020-03-24 18:36:56 +0100190 sys.exit(1)
Gilles Peskine54f54452019-05-27 18:31:59 +0200191
192if __name__ == '__main__':
193 main()