blob: 921c63522fc9945d110a1bcd59f4670590f3db1b [file] [log] [blame]
Gilles Peskine693611e2024-06-11 19:32:22 +02001#!/usr/bin/env python3
2"""Generate test data for configuration reporting.
3"""
4
5# Copyright The Mbed TLS Contributors
6# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
7
8import re
9import sys
10from typing import Iterable, Iterator, List, Optional, Tuple
11
12import project_scripts # pylint: disable=unused-import
13import config
14from mbedtls_framework import test_case
15from mbedtls_framework import test_data_generation
16
17
18def single_option_case(setting: config.Setting, when_on: bool,
19 dependencies: List[str],
20 note: Optional[str]) -> test_case.TestCase:
21 """Construct a test case for a boolean setting.
22
23 This test case passes if the setting and its dependencies are enabled,
24 and is skipped otherwise.
25
26 * setting: the setting to be tested.
27 * when_on: True to test with the setting enabled, or False to test
28 with the setting disabled.
29 * dependencies: extra dependencies for the test case.
30 * note: a note to add after the option name in the test description.
31 This is generally a summary of dependencies, and is generally empty
32 if the given setting is only tested once.
33 """
34 base = setting.name if when_on else '!' + setting.name
35 tc = test_case.TestCase()
36 tc.set_function('pass')
37 description_suffix = ' (' + note + ')' if note else ''
38 tc.set_description('Config: ' + base + description_suffix)
39 tc.set_dependencies([base] + dependencies)
40 return tc
41
42
Gilles Peskinec79ecea2024-05-23 16:32:39 +020043PSA_WANT_KEY_TYPE_KEY_PAIR_RE = \
44 re.compile(r'(?P<prefix>PSA_WANT_KEY_TYPE_(?P<type>\w+)_KEY_PAIR_)(?P<operation>\w+)\Z')
45
46# If foo is an option that is only meaningful when bar is enabled, set
47# SUPER_SETTINGS[foo]=bar. More generally, bar can be a colon-separated
48# list of options, meaning that all the options must be enabled. Each option
49# can be prefixed with '!' to negate it. This is the same syntax as a
50# depends_on directive in test data.
51# See also `find_super_option`.
52SUPER_SETTINGS = {
53 'MBEDTLS_AESCE_C': 'MBEDTLS_AES_C',
54 'MBEDTLS_AESNI_C': 'MBEDTLS_AES_C',
55 'MBEDTLS_ERROR_STRERROR_DUMMY': '!MBEDTLS_ERROR_C',
56 'MBEDTLS_GENPRIME': 'MBEDTLS_RSA_C',
57 'MBEDTLS_NO_DEFAULT_ENTROPY_SOURCES': 'MBEDTLS_ENTROPY_C',
58 'MBEDTLS_NO_PLATFORM_ENTROPY': 'MBEDTLS_ENTROPY_C',
59 'MBEDTLS_PKCS1_V15': 'MBEDTLS_RSA_C',
60 'MBEDTLS_PKCS1_V21': 'MBEDTLS_RSA_C',
Gilles Peskinec08d5bf2024-05-28 19:18:31 +020061 'MBEDTLS_PSA_CRYPTO_CLIENT': '!MBEDTLS_PSA_CRYPTO_C',
Gilles Peskinec79ecea2024-05-23 16:32:39 +020062 'MBEDTLS_PSA_INJECT_ENTROPY': 'MBEDTLS_PSA_CRYPTO_C',
63 'MBEDTLS_PSA_ASSUME_EXCLUSIVE_BUFFERS': 'MBEDTLS_PSA_CRYPTO_C',
64}
65
66def find_super_option(cfg: config.Config,
67 setting: config.Setting) -> Optional[str]:
68 """If setting is only meaningful when some option is enabled, return that option.
69
70 The return value can be a colon-separated list of options, if the setting
71 is only meaningful when all of these options are enabled. Options can be
72 negated by prefixing them with '!'. This is the same syntax as a
73 depends_on directive in test data.
74 """
75 #pylint: disable=too-many-return-statements
76 name = setting.name
77 if name in SUPER_SETTINGS:
78 return SUPER_SETTINGS[name]
79 if name.startswith('MBEDTLS_') and not name.endswith('_C'):
80 if name.startswith('MBEDTLS_CIPHER_PADDING_'):
81 return 'MBEDTLS_CIPHER_C:MBEDTLS_CIPHER_MODE_CBC'
82 if name.startswith('MBEDTLS_PK_PARSE_EC_'):
83 return 'MBEDTLS_PK_C:MBEDTLS_PK_HAVE_ECC_KEYS'
Gilles Peskinef75c70b2024-05-28 19:18:46 +020084 # For TLS options, insist on having them once off and once on in
85 # a configuration where both client support and server support are
86 # enabled. The options are also meaningful when only one side is
87 # enabled, but there isn't much point in having separate records
88 # for client-side and server-side, so we keep things simple.
89 # Requiring both sides to be enabled also means we know we'll run
90 # tests that only run Mbed TLS against itself, which only run in
91 # configurations with both sides enabled.
Gilles Peskinec79ecea2024-05-23 16:32:39 +020092 if name.startswith('MBEDTLS_SSL_TLS1_3_') or \
93 name == 'MBEDTLS_SSL_EARLY_DATA':
94 return 'MBEDTLS_SSL_CLI_C:MBEDTLS_SSL_SRV_C:MBEDTLS_SSL_PROTO_TLS1_3'
95 if name.startswith('MBEDTLS_SSL_DTLS_'):
96 return 'MBEDTLS_SSL_CLI_C:MBEDTLS_SSL_SRV_C:MBEDTLS_SSL_PROTO_DTLS'
97 if name.startswith('MBEDTLS_SSL_'):
98 return 'MBEDTLS_SSL_CLI_C:MBEDTLS_SSL_SRV_C'
Gilles Peskine556249e2024-05-23 19:37:20 +020099 for pos in re.finditer(r'_', name):
100 super_name = name[:pos.start()] + '_C'
Gilles Peskinec79ecea2024-05-23 16:32:39 +0200101 if cfg.known(super_name):
102 return super_name
Gilles Peskine556249e2024-05-23 19:37:20 +0200103 m = PSA_WANT_KEY_TYPE_KEY_PAIR_RE.match(name)
Gilles Peskinec79ecea2024-05-23 16:32:39 +0200104 if m and m.group('operation') != 'BASIC':
105 return m.group('prefix') + 'BASIC'
106 return None
107
Gilles Peskine693611e2024-06-11 19:32:22 +0200108def conditions_for_option(cfg: config.Config,
109 setting: config.Setting
110 ) -> Iterator[Tuple[List[str], str]]:
111 """Enumerate the conditions under which to test the given setting.
112
113 * cfg: all configuration options.
114 * setting: the setting to be tested.
115
116 Generate a stream of conditions, i.e. extra dependencies to test with
117 together with a human-readable explanation of each dependency. Some
118 typical cases:
119
120 * By default, generate a one-element stream with no extra dependencies.
121 * If the setting is ignored unless some other option is enabled, generate
122 a one-element stream with that other option as an extra dependency.
123 * If the setting is known to interact with some other option, generate
124 a stream with one element where this option is on and one where it's off.
125 * To skip the setting altogether, generate an empty stream.
126 """
127 name = setting.name
128 if name.endswith('_ALT') and not config.is_seamless_alt(name):
129 # We don't test alt implementations, except (most) platform alts
130 return
Gilles Peskinec79ecea2024-05-23 16:32:39 +0200131 super_setting = find_super_option(cfg, setting)
132 if super_setting:
133 yield [super_setting], ''
134 return
Gilles Peskine693611e2024-06-11 19:32:22 +0200135 yield [], ''
136
137
138def enumerate_boolean_option_cases(cfg: config.Config
139 ) -> Iterable[test_case.TestCase]:
140 """Emit test cases for all boolean options."""
141 for name in sorted(cfg.settings.keys()):
142 setting = cfg.settings[name]
143 if not name.startswith('PSA_WANT_') and setting.value:
144 continue # non-boolean setting
145 for when_on in True, False:
146 for deps, note in conditions_for_option(cfg, setting):
147 yield single_option_case(setting, when_on, deps, note)
148
149
150
151class ConfigTestGenerator(test_data_generation.TestGenerator):
152 """Generate test cases for configuration reporting."""
153
154 def __init__(self, options):
155 self.mbedtls_config = config.ConfigFile()
156 self.targets['test_suite_config.mbedtls_boolean'] = \
157 lambda: enumerate_boolean_option_cases(self.mbedtls_config)
158 self.psa_config = config.ConfigFile('include/psa/crypto_config.h')
159 self.targets['test_suite_config.psa_boolean'] = \
160 lambda: enumerate_boolean_option_cases(self.psa_config)
161 super().__init__(options)
162
163
164if __name__ == '__main__':
165 test_data_generation.main(sys.argv[1:], __doc__, ConfigTestGenerator)