blob: e43a0baef2684e94bcdfb4930ab749584239b02b [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 os
Gilles Peskine24827022018-09-25 18:49:23 +020028import re
29import subprocess
30import sys
Gilles Peskineb4edff92021-03-30 19:09:05 +020031from typing import Iterable, List, Optional, Tuple
Gilles Peskine2adebc82020-12-11 00:30:53 +010032
33import scripts_path # pylint: disable=unused-import
34from mbedtls_dev import c_build_helper
Gilles Peskineb4edff92021-03-30 19:09:05 +020035from mbedtls_dev.macro_collector import InputsForTest, PSAMacroEnumerator
Gilles Peskine95649ed2021-03-29 20:37:40 +020036from mbedtls_dev import typing_util
Gilles Peskine6f7ba5f2021-03-10 00:50:18 +010037
Gilles Peskine95649ed2021-03-29 20:37:40 +020038def gather_inputs(headers: Iterable[str],
39 test_suites: Iterable[str],
40 inputs_class=InputsForTest) -> PSAMacroEnumerator:
Gilles Peskinea3b93ff2019-06-03 11:23:56 +020041 """Read the list of inputs to test psa_constant_names with."""
Gilles Peskine84a45812019-11-21 19:50:33 +010042 inputs = inputs_class()
Gilles Peskine24827022018-09-25 18:49:23 +020043 for header in headers:
44 inputs.parse_header(header)
45 for test_cases in test_suites:
46 inputs.parse_test_cases(test_cases)
Gilles Peskine3d404b82021-03-30 21:46:35 +020047 inputs.add_numerical_values()
Gilles Peskine24827022018-09-25 18:49:23 +020048 inputs.gather_arguments()
49 return inputs
50
Gilles Peskine95649ed2021-03-29 20:37:40 +020051def run_c(type_word: str,
52 expressions: Iterable[str],
53 include_path: Optional[str] = None,
54 keep_c: bool = False) -> List[str]:
Gilles Peskine2991b5f2021-01-19 21:19:02 +010055 """Generate and run a program to print out numerical values of C expressions."""
Gilles Peskine42a0a0a2019-05-27 18:29:47 +020056 if type_word == 'status':
Gilles Peskinec4cd2ad2019-02-13 18:42:53 +010057 cast_to = 'long'
58 printf_format = '%ld'
59 else:
60 cast_to = 'unsigned long'
61 printf_format = '0x%08lx'
Gilles Peskine2adebc82020-12-11 00:30:53 +010062 return c_build_helper.get_c_expression_values(
Gilles Peskinefc622112020-12-11 00:27:14 +010063 cast_to, printf_format,
64 expressions,
65 caller='test_psa_constant_names.py for {} values'.format(type_word),
66 file_label=type_word,
67 header='#include <psa/crypto.h>',
68 include_path=include_path,
69 keep_c=keep_c
70 )
Gilles Peskine24827022018-09-25 18:49:23 +020071
Gilles Peskine42a0a0a2019-05-27 18:29:47 +020072NORMALIZE_STRIP_RE = re.compile(r'\s+')
Gilles Peskine95649ed2021-03-29 20:37:40 +020073def normalize(expr: str) -> str:
Gilles Peskinea3b93ff2019-06-03 11:23:56 +020074 """Normalize the C expression so as not to care about trivial differences.
Gilles Peskine4408dfd2019-11-21 17:16:21 +010075
Gilles Peskinea3b93ff2019-06-03 11:23:56 +020076 Currently "trivial differences" means whitespace.
77 """
Gilles Peskine5a6dc892019-11-21 16:48:07 +010078 return re.sub(NORMALIZE_STRIP_RE, '', expr)
Gilles Peskine24827022018-09-25 18:49:23 +020079
Gilles Peskine17e350b2022-03-19 18:06:52 +010080ALG_TRUNCATED_TO_SELF_RE = \
81 re.compile(r'PSA_ALG_AEAD_WITH_SHORTENED_TAG\('
82 r'PSA_ALG_(?:CCM|CHACHA20_POLY1305|GCM)'
83 r', *16\)\Z')
84
85def is_simplifiable(expr: str) -> bool:
86 """Determine whether an expression is simplifiable.
87
88 Simplifiable expressions can't be output in their input form, since
89 the output will be the simple form. Therefore they must be excluded
90 from testing.
91 """
92 if ALG_TRUNCATED_TO_SELF_RE.match(expr):
93 return True
94 return False
95
Gilles Peskine95649ed2021-03-29 20:37:40 +020096def collect_values(inputs: InputsForTest,
97 type_word: str,
98 include_path: Optional[str] = None,
99 keep_c: bool = False) -> Tuple[List[str], List[str]]:
Gilles Peskinec2317112019-11-21 17:17:39 +0100100 """Generate expressions using known macro names and calculate their values.
101
102 Return a list of pairs of (expr, value) where expr is an expression and
103 value is a string representation of its integer value.
104 """
105 names = inputs.get_names(type_word)
Gilles Peskine17e350b2022-03-19 18:06:52 +0100106 expressions = sorted(expr
107 for expr in inputs.generate_expressions(names)
108 if not is_simplifiable(expr))
Gilles Peskineb86b6d32019-11-21 17:26:10 +0100109 values = run_c(type_word, expressions,
110 include_path=include_path, keep_c=keep_c)
Gilles Peskinec2317112019-11-21 17:17:39 +0100111 return expressions, values
112
Gilles Peskine24609332019-11-21 17:44:21 +0100113class Tests:
114 """An object representing tests and their results."""
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100115
Gilles Peskinea5000f12019-11-21 17:51:11 +0100116 Error = namedtuple('Error',
117 ['type', 'expression', 'value', 'output'])
118
Gilles Peskine95649ed2021-03-29 20:37:40 +0200119 def __init__(self, options) -> None:
Gilles Peskine24609332019-11-21 17:44:21 +0100120 self.options = options
121 self.count = 0
Gilles Peskine95649ed2021-03-29 20:37:40 +0200122 self.errors = [] #type: List[Tests.Error]
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100123
Gilles Peskine95649ed2021-03-29 20:37:40 +0200124 def run_one(self, inputs: InputsForTest, type_word: str) -> None:
Gilles Peskine24609332019-11-21 17:44:21 +0100125 """Test psa_constant_names for the specified type.
Gilles Peskine24827022018-09-25 18:49:23 +0200126
Gilles Peskine24609332019-11-21 17:44:21 +0100127 Run the program on the names for this type.
128 Use the inputs to figure out what arguments to pass to macros that
129 take arguments.
130 """
131 expressions, values = collect_values(inputs, type_word,
132 include_path=self.options.include,
133 keep_c=self.options.keep_c)
Gilles Peskine95649ed2021-03-29 20:37:40 +0200134 output_bytes = subprocess.check_output([self.options.program,
135 type_word] + values)
136 output = output_bytes.decode('ascii')
137 outputs = output.strip().split('\n')
Gilles Peskine24609332019-11-21 17:44:21 +0100138 self.count += len(expressions)
139 for expr, value, output in zip(expressions, values, outputs):
Gilles Peskine32558482019-12-03 19:03:35 +0100140 if self.options.show:
141 sys.stdout.write('{} {}\t{}\n'.format(type_word, value, output))
Gilles Peskine24609332019-11-21 17:44:21 +0100142 if normalize(expr) != normalize(output):
Gilles Peskinea5000f12019-11-21 17:51:11 +0100143 self.errors.append(self.Error(type=type_word,
144 expression=expr,
145 value=value,
146 output=output))
Gilles Peskine24827022018-09-25 18:49:23 +0200147
Gilles Peskine95649ed2021-03-29 20:37:40 +0200148 def run_all(self, inputs: InputsForTest) -> None:
Gilles Peskine24609332019-11-21 17:44:21 +0100149 """Run psa_constant_names on all the gathered inputs."""
150 for type_word in ['status', 'algorithm', 'ecc_curve', 'dh_group',
151 'key_type', 'key_usage']:
152 self.run_one(inputs, type_word)
Gilles Peskine4408dfd2019-11-21 17:16:21 +0100153
Gilles Peskine95649ed2021-03-29 20:37:40 +0200154 def report(self, out: typing_util.Writable) -> None:
Gilles Peskine24609332019-11-21 17:44:21 +0100155 """Describe each case where the output is not as expected.
156
157 Write the errors to ``out``.
158 Also write a total.
159 """
Gilles Peskinea5000f12019-11-21 17:51:11 +0100160 for error in self.errors:
Gilles Peskine24609332019-11-21 17:44:21 +0100161 out.write('For {} "{}", got "{}" (value: {})\n'
Gilles Peskinea5000f12019-11-21 17:51:11 +0100162 .format(error.type, error.expression,
163 error.output, error.value))
Gilles Peskine24609332019-11-21 17:44:21 +0100164 out.write('{} test cases'.format(self.count))
165 if self.errors:
166 out.write(', {} FAIL\n'.format(len(self.errors)))
167 else:
168 out.write(' PASS\n')
Gilles Peskine24827022018-09-25 18:49:23 +0200169
Gilles Peskine69f93b52019-11-21 16:49:50 +0100170HEADERS = ['psa/crypto.h', 'psa/crypto_extra.h', 'psa/crypto_values.h']
171TEST_SUITES = ['tests/suites/test_suite_psa_crypto_metadata.data']
172
Gilles Peskine54f54452019-05-27 18:31:59 +0200173def main():
Gilles Peskine24827022018-09-25 18:49:23 +0200174 parser = argparse.ArgumentParser(description=globals()['__doc__'])
175 parser.add_argument('--include', '-I',
176 action='append', default=['include'],
177 help='Directory for header files')
Gilles Peskinecf9c18e2018-10-19 11:28:42 +0200178 parser.add_argument('--keep-c',
179 action='store_true', dest='keep_c', default=False,
180 help='Keep the intermediate C file')
181 parser.add_argument('--no-keep-c',
182 action='store_false', dest='keep_c',
183 help='Don\'t keep the intermediate C file (default)')
Gilles Peskine8f5a5012019-11-21 16:49:10 +0100184 parser.add_argument('--program',
185 default='programs/psa/psa_constant_names',
186 help='Program to test')
Gilles Peskine32558482019-12-03 19:03:35 +0100187 parser.add_argument('--show',
188 action='store_true',
Gilles Peskinec893a572021-03-17 13:45:32 +0100189 help='Show tested values on stdout')
Gilles Peskine32558482019-12-03 19:03:35 +0100190 parser.add_argument('--no-show',
191 action='store_false', dest='show',
192 help='Don\'t show tested values (default)')
Gilles Peskine24827022018-09-25 18:49:23 +0200193 options = parser.parse_args()
Gilles Peskine69f93b52019-11-21 16:49:50 +0100194 headers = [os.path.join(options.include[0], h) for h in HEADERS]
195 inputs = gather_inputs(headers, TEST_SUITES)
Gilles Peskine24609332019-11-21 17:44:21 +0100196 tests = Tests(options)
197 tests.run_all(inputs)
198 tests.report(sys.stdout)
199 if tests.errors:
Gilles Peskine8b022352020-03-24 18:36:56 +0100200 sys.exit(1)
Gilles Peskine54f54452019-05-27 18:31:59 +0200201
202if __name__ == '__main__':
203 main()