blob: e3c1d8ddcb50733cdfc0e96f12b9e0211408c4ee [file] [log] [blame]
Minos Galanakis2c824b42025-03-20 09:28:45 +00001#!/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 inspect
9import re
10import sys
11from typing import Iterable, Iterator, List, Optional, Tuple
12
13import project_scripts # pylint: disable=unused-import
14import config
15from mbedtls_framework import config_common
16from mbedtls_framework import test_case
17from mbedtls_framework import test_data_generation
18
19
20def single_setting_case(setting: config_common.Setting, when_on: bool,
21 dependencies: List[str],
22 note: Optional[str]) -> test_case.TestCase:
23 """Construct a test case for a boolean setting.
24
25 This test case passes if the setting and its dependencies are enabled,
26 and is skipped otherwise.
27
28 * setting: the setting to be tested.
29 * when_on: True to test with the setting enabled, or False to test
30 with the setting disabled.
31 * dependencies: extra dependencies for the test case.
32 * note: a note to add after the setting name in the test description.
33 This is generally a summary of dependencies, and is generally empty
34 if the given setting is only tested once.
35 """
36 base = setting.name if when_on else '!' + setting.name
37 tc = test_case.TestCase()
38 tc.set_function('pass')
39 description_suffix = ' (' + note + ')' if note else ''
40 tc.set_description('Config: ' + base + description_suffix)
41 tc.set_dependencies([base] + dependencies)
42 return tc
43
44
45PSA_WANT_KEY_TYPE_KEY_PAIR_RE = \
46 re.compile(r'(?P<prefix>PSA_WANT_KEY_TYPE_(?P<type>\w+)_KEY_PAIR_)(?P<operation>\w+)\Z')
47
48# If foo is a setting that is only meaningful when bar is enabled, set
49# SIMPLE_DEPENDENCIES[foo]=bar. More generally, bar can be a colon-separated
50# list of settings, meaning that all the settings must be enabled. Each setting
51# in bar can be prefixed with '!' to negate it. This is the same syntax as a
52# depends_on directive in test data.
53# See also `dependencies_of_settting`.
54SIMPLE_DEPENDENCIES = {
55 'MBEDTLS_AESCE_C': 'MBEDTLS_AES_C',
56 'MBEDTLS_AESNI_C': 'MBEDTLS_AES_C',
57 'MBEDTLS_ERROR_STRERROR_DUMMY': '!MBEDTLS_ERROR_C',
58 'MBEDTLS_GENPRIME': 'MBEDTLS_RSA_C',
59 'MBEDTLS_NO_DEFAULT_ENTROPY_SOURCES': 'MBEDTLS_ENTROPY_C',
60 'MBEDTLS_NO_PLATFORM_ENTROPY': 'MBEDTLS_ENTROPY_C',
61 'MBEDTLS_PKCS1_V15': 'MBEDTLS_RSA_C',
62 'MBEDTLS_PKCS1_V21': 'MBEDTLS_RSA_C',
63 'MBEDTLS_PSA_CRYPTO_CLIENT': '!MBEDTLS_PSA_CRYPTO_C',
64 'MBEDTLS_PSA_INJECT_ENTROPY': 'MBEDTLS_PSA_CRYPTO_C',
65 'MBEDTLS_PSA_ASSUME_EXCLUSIVE_BUFFERS': 'MBEDTLS_PSA_CRYPTO_C',
66}
67
68def dependencies_of_setting(cfg: config_common.Config,
69 setting: config_common.Setting) -> Optional[str]:
70 """Return dependencies without which a setting is not meaningful.
71
72 The dependencies of a setting express when a setting can be enabled and
73 is relevant. For example, if ``check_config.h`` errors out when
74 ``defined(FOO) && !defined(BAR)``, then ``BAR`` is a dependency of ``FOO``.
75 If ``FOO`` has no effect when ``CORGE`` is disabled, then ``CORGE``
76 is a dependency of ``FOO``.
77
78 The return value can be a colon-separated list of settings, if the setting
79 is only meaningful when all of these settings are enabled. Each setting can
80 be negated by prefixing them with '!'. This is the same syntax as a
81 depends_on directive in test data.
82 """
83 #pylint: disable=too-many-branches,too-many-return-statements
84 name = setting.name
85 if name in SIMPLE_DEPENDENCIES:
86 return SIMPLE_DEPENDENCIES[name]
87 if name.startswith('MBEDTLS_') and not name.endswith('_C'):
88 if name.startswith('MBEDTLS_CIPHER_PADDING_'):
89 return 'MBEDTLS_CIPHER_C:MBEDTLS_CIPHER_MODE_CBC'
90 if name.startswith('MBEDTLS_PK_PARSE_EC_'):
91 return 'MBEDTLS_PK_C:' + test_case.psa_or_3_6_feature_macro(
92 'PSA_KEY_TYPE_ECC_PUBLIC_KEY', test_case.Domain36.USE_PSA)
93
94 # For TLS settings, insist on having them once off and once on in
95 # a configuration where both client support and server support are
96 # enabled. The settings are also meaningful when only one side is
97 # enabled, but there isn't much point in having separate records
98 # for client-side and server-side, so we keep things simple.
99 # Requiring both sides to be enabled also means we know we'll run
100 # tests that only run Mbed TLS against itself, which only run in
101 # configurations with both sides enabled.
102 if name.startswith('MBEDTLS_SSL_TLS1_3_') or \
103 name == 'MBEDTLS_SSL_EARLY_DATA':
104 return 'MBEDTLS_SSL_CLI_C:MBEDTLS_SSL_SRV_C:MBEDTLS_SSL_PROTO_TLS1_3'
105 if name.startswith('MBEDTLS_SSL_DTLS_'):
106 return 'MBEDTLS_SSL_CLI_C:MBEDTLS_SSL_SRV_C:MBEDTLS_SSL_PROTO_DTLS'
107 if name.startswith('MBEDTLS_SSL_'):
108 return 'MBEDTLS_SSL_CLI_C:MBEDTLS_SSL_SRV_C'
109 for pos in re.finditer(r'_', name):
110 super_name = name[:pos.start()] + '_C'
111 if cfg.known(super_name):
112 return super_name
113 if name.startswith('PSA_WANT_'):
114 deps = 'MBEDTLS_PSA_CRYPTO_CLIENT'
115 m = PSA_WANT_KEY_TYPE_KEY_PAIR_RE.match(name)
116 if m and m.group('operation') != 'BASIC':
117 deps += ':' + m.group('prefix') + 'BASIC'
118 return deps
119 return None
120
121def conditions_for_setting(cfg: config_common.Config,
122 setting: config_common.Setting
123 ) -> Iterator[Tuple[List[str], str]]:
124 """Enumerate the conditions under which to test the given setting.
125
126 * cfg: all configuration settings.
127 * setting: the setting to be tested.
128
129 Generate a stream of conditions, i.e. extra dependencies to test with
130 together with a human-readable explanation of each dependency. Some
131 typical cases:
132
133 * By default, generate a one-element stream with no extra dependencies.
134 * If the setting is ignored unless some other setting is enabled, generate
135 a one-element stream with that other setting as an extra dependency.
136 * If the setting is known to interact with some other setting, generate
137 a stream with one element where this setting is on and one where it's off.
138 * To skip the setting altogether, generate an empty stream.
139 """
140 name = setting.name
141 if name.endswith('_ALT') and not config.is_seamless_alt(name):
142 # We don't test alt implementations, except (most) platform alts
143 return
144 dependencies = dependencies_of_setting(cfg, setting)
145 if dependencies:
146 yield [dependencies], ''
147 return
148 yield [], ''
149
150
151def enumerate_boolean_setting_cases(cfg: config_common.Config
152 ) -> Iterable[test_case.TestCase]:
153 """Emit test cases for all boolean settings."""
154 for name in sorted(cfg.settings.keys()):
155 setting = cfg.settings[name]
156 if not name.startswith('PSA_WANT_') and setting.value:
157 continue # non-boolean setting
158 for when_on in True, False:
159 for deps, note in conditions_for_setting(cfg, setting):
160 yield single_setting_case(setting, when_on, deps, note)
161
162
163
164class ConfigTestGenerator(test_data_generation.TestGenerator):
165 """Generate test cases for configuration reporting."""
166
167 def __init__(self, settings):
168 # pylint: disable=no-member
169 config_members = dict(inspect.getmembers(config))
170 if 'MbedTLSConfig' in config_members:
171 self.mbedtls_config = config.MbedTLSConfig()
172 self.targets['test_suite_config.mbedtls_boolean'] = \
173 lambda: enumerate_boolean_setting_cases(self.mbedtls_config)
174 if 'CryptoConfig' in config_members:
175 self.psa_config = config.CryptoConfig()
176 self.targets['test_suite_config.psa_boolean'] = \
177 lambda: enumerate_boolean_setting_cases(self.psa_config)
178 elif 'TFPSACryptoConfig' in config_members:
179 self.psa_config = config.TFPSACryptoConfig()
180 self.targets['test_suite_config.psa_boolean'] = \
181 lambda: enumerate_boolean_setting_cases(self.psa_config)
182 super().__init__(settings)
183
184
185if __name__ == '__main__':
186 test_data_generation.main(sys.argv[1:], __doc__, ConfigTestGenerator)