Gilles Peskine | 693611e | 2024-06-11 19:32:22 +0200 | [diff] [blame] | 1 | #!/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 | |
| 8 | import re |
| 9 | import sys |
| 10 | from typing import Iterable, Iterator, List, Optional, Tuple |
| 11 | |
Gilles Peskine | 744741b | 2024-06-26 20:05:10 +0200 | [diff] [blame] | 12 | import scripts_path # pylint: disable=unused-import |
Gilles Peskine | 693611e | 2024-06-11 19:32:22 +0200 | [diff] [blame] | 13 | import config |
Gilles Peskine | 744741b | 2024-06-26 20:05:10 +0200 | [diff] [blame] | 14 | from mbedtls_dev import test_case |
| 15 | from mbedtls_dev import test_data_generation |
Gilles Peskine | 693611e | 2024-06-11 19:32:22 +0200 | [diff] [blame] | 16 | |
| 17 | |
Gilles Peskine | 5454a84 | 2024-05-29 16:37:38 +0200 | [diff] [blame] | 18 | def single_setting_case(setting: config.Setting, when_on: bool, |
| 19 | dependencies: List[str], |
| 20 | note: Optional[str]) -> test_case.TestCase: |
Gilles Peskine | 693611e | 2024-06-11 19:32:22 +0200 | [diff] [blame] | 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. |
Gilles Peskine | 5454a84 | 2024-05-29 16:37:38 +0200 | [diff] [blame] | 30 | * note: a note to add after the setting name in the test description. |
Gilles Peskine | 693611e | 2024-06-11 19:32:22 +0200 | [diff] [blame] | 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 Peskine | 5454a84 | 2024-05-29 16:37:38 +0200 | [diff] [blame] | 43 | # If foo is a setting that is only meaningful when bar is enabled, set |
Gilles Peskine | 1afb703 | 2024-05-29 16:44:52 +0200 | [diff] [blame] | 44 | # SIMPLE_DEPENDENCIES[foo]=bar. More generally, bar can be a colon-separated |
Gilles Peskine | 5454a84 | 2024-05-29 16:37:38 +0200 | [diff] [blame] | 45 | # list of settings, meaning that all the settings must be enabled. Each setting |
Gilles Peskine | 1afb703 | 2024-05-29 16:44:52 +0200 | [diff] [blame] | 46 | # in bar can be prefixed with '!' to negate it. This is the same syntax as a |
Gilles Peskine | c79ecea | 2024-05-23 16:32:39 +0200 | [diff] [blame] | 47 | # depends_on directive in test data. |
Gilles Peskine | 1afb703 | 2024-05-29 16:44:52 +0200 | [diff] [blame] | 48 | # See also `dependencies_of_settting`. |
| 49 | SIMPLE_DEPENDENCIES = { |
Gilles Peskine | c79ecea | 2024-05-23 16:32:39 +0200 | [diff] [blame] | 50 | 'MBEDTLS_AESNI_C': 'MBEDTLS_AES_C', |
| 51 | 'MBEDTLS_ERROR_STRERROR_DUMMY': '!MBEDTLS_ERROR_C', |
| 52 | 'MBEDTLS_GENPRIME': 'MBEDTLS_RSA_C', |
| 53 | 'MBEDTLS_NO_DEFAULT_ENTROPY_SOURCES': 'MBEDTLS_ENTROPY_C', |
| 54 | 'MBEDTLS_NO_PLATFORM_ENTROPY': 'MBEDTLS_ENTROPY_C', |
| 55 | 'MBEDTLS_PKCS1_V15': 'MBEDTLS_RSA_C', |
| 56 | 'MBEDTLS_PKCS1_V21': 'MBEDTLS_RSA_C', |
Gilles Peskine | c08d5bf | 2024-05-28 19:18:31 +0200 | [diff] [blame] | 57 | 'MBEDTLS_PSA_CRYPTO_CLIENT': '!MBEDTLS_PSA_CRYPTO_C', |
Gilles Peskine | c79ecea | 2024-05-23 16:32:39 +0200 | [diff] [blame] | 58 | 'MBEDTLS_PSA_INJECT_ENTROPY': 'MBEDTLS_PSA_CRYPTO_C', |
| 59 | 'MBEDTLS_PSA_ASSUME_EXCLUSIVE_BUFFERS': 'MBEDTLS_PSA_CRYPTO_C', |
| 60 | } |
| 61 | |
Gilles Peskine | 1afb703 | 2024-05-29 16:44:52 +0200 | [diff] [blame] | 62 | def dependencies_of_setting(cfg: config.Config, |
| 63 | setting: config.Setting) -> Optional[str]: |
| 64 | """Return dependencies without which a setting is not meaningful. |
| 65 | |
| 66 | The dependencies of a setting express when a setting can be enabled and |
| 67 | is relevant. For example, if ``check_config.h`` errors out when |
| 68 | ``defined(FOO) && !defined(BAR)``, then ``BAR`` is a dependency of ``FOO``. |
| 69 | If ``FOO`` has no effect when ``CORGE`` is disabled, then ``CORGE`` |
| 70 | is a dependency of ``FOO``. |
Gilles Peskine | c79ecea | 2024-05-23 16:32:39 +0200 | [diff] [blame] | 71 | |
Gilles Peskine | 5454a84 | 2024-05-29 16:37:38 +0200 | [diff] [blame] | 72 | The return value can be a colon-separated list of settings, if the setting |
Gilles Peskine | 1afb703 | 2024-05-29 16:44:52 +0200 | [diff] [blame] | 73 | is only meaningful when all of these settings are enabled. Each setting can |
| 74 | be negated by prefixing them with '!'. This is the same syntax as a |
Gilles Peskine | c79ecea | 2024-05-23 16:32:39 +0200 | [diff] [blame] | 75 | depends_on directive in test data. |
| 76 | """ |
| 77 | #pylint: disable=too-many-return-statements |
| 78 | name = setting.name |
Gilles Peskine | 1afb703 | 2024-05-29 16:44:52 +0200 | [diff] [blame] | 79 | if name in SIMPLE_DEPENDENCIES: |
| 80 | return SIMPLE_DEPENDENCIES[name] |
Gilles Peskine | c79ecea | 2024-05-23 16:32:39 +0200 | [diff] [blame] | 81 | if name.startswith('MBEDTLS_') and not name.endswith('_C'): |
| 82 | if name.startswith('MBEDTLS_CIPHER_PADDING_'): |
| 83 | return 'MBEDTLS_CIPHER_C:MBEDTLS_CIPHER_MODE_CBC' |
| 84 | if name.startswith('MBEDTLS_PK_PARSE_EC_'): |
| 85 | return 'MBEDTLS_PK_C:MBEDTLS_PK_HAVE_ECC_KEYS' |
Gilles Peskine | 5454a84 | 2024-05-29 16:37:38 +0200 | [diff] [blame] | 86 | # For TLS settings, insist on having them once off and once on in |
Gilles Peskine | f75c70b | 2024-05-28 19:18:46 +0200 | [diff] [blame] | 87 | # a configuration where both client support and server support are |
Gilles Peskine | 5454a84 | 2024-05-29 16:37:38 +0200 | [diff] [blame] | 88 | # enabled. The settings are also meaningful when only one side is |
Gilles Peskine | f75c70b | 2024-05-28 19:18:46 +0200 | [diff] [blame] | 89 | # enabled, but there isn't much point in having separate records |
| 90 | # for client-side and server-side, so we keep things simple. |
| 91 | # Requiring both sides to be enabled also means we know we'll run |
| 92 | # tests that only run Mbed TLS against itself, which only run in |
| 93 | # configurations with both sides enabled. |
Gilles Peskine | cfba247 | 2024-06-26 20:11:59 +0200 | [diff] [blame^] | 94 | if name.startswith('MBEDTLS_SSL_TLS1_3_'): |
Gilles Peskine | c79ecea | 2024-05-23 16:32:39 +0200 | [diff] [blame] | 95 | return 'MBEDTLS_SSL_CLI_C:MBEDTLS_SSL_SRV_C:MBEDTLS_SSL_PROTO_TLS1_3' |
| 96 | if name.startswith('MBEDTLS_SSL_DTLS_'): |
| 97 | return 'MBEDTLS_SSL_CLI_C:MBEDTLS_SSL_SRV_C:MBEDTLS_SSL_PROTO_DTLS' |
| 98 | if name.startswith('MBEDTLS_SSL_'): |
| 99 | return 'MBEDTLS_SSL_CLI_C:MBEDTLS_SSL_SRV_C' |
Gilles Peskine | 556249e | 2024-05-23 19:37:20 +0200 | [diff] [blame] | 100 | for pos in re.finditer(r'_', name): |
| 101 | super_name = name[:pos.start()] + '_C' |
Gilles Peskine | c79ecea | 2024-05-23 16:32:39 +0200 | [diff] [blame] | 102 | if cfg.known(super_name): |
| 103 | return super_name |
Gilles Peskine | c79ecea | 2024-05-23 16:32:39 +0200 | [diff] [blame] | 104 | return None |
| 105 | |
Gilles Peskine | 5454a84 | 2024-05-29 16:37:38 +0200 | [diff] [blame] | 106 | def conditions_for_setting(cfg: config.Config, |
| 107 | setting: config.Setting |
| 108 | ) -> Iterator[Tuple[List[str], str]]: |
Gilles Peskine | 693611e | 2024-06-11 19:32:22 +0200 | [diff] [blame] | 109 | """Enumerate the conditions under which to test the given setting. |
| 110 | |
Gilles Peskine | 5454a84 | 2024-05-29 16:37:38 +0200 | [diff] [blame] | 111 | * cfg: all configuration settings. |
Gilles Peskine | 693611e | 2024-06-11 19:32:22 +0200 | [diff] [blame] | 112 | * setting: the setting to be tested. |
| 113 | |
| 114 | Generate a stream of conditions, i.e. extra dependencies to test with |
| 115 | together with a human-readable explanation of each dependency. Some |
| 116 | typical cases: |
| 117 | |
| 118 | * By default, generate a one-element stream with no extra dependencies. |
Gilles Peskine | 5454a84 | 2024-05-29 16:37:38 +0200 | [diff] [blame] | 119 | * If the setting is ignored unless some other setting is enabled, generate |
| 120 | a one-element stream with that other setting as an extra dependency. |
| 121 | * If the setting is known to interact with some other setting, generate |
| 122 | a stream with one element where this setting is on and one where it's off. |
Gilles Peskine | 693611e | 2024-06-11 19:32:22 +0200 | [diff] [blame] | 123 | * To skip the setting altogether, generate an empty stream. |
| 124 | """ |
| 125 | name = setting.name |
| 126 | if name.endswith('_ALT') and not config.is_seamless_alt(name): |
| 127 | # We don't test alt implementations, except (most) platform alts |
| 128 | return |
Gilles Peskine | 1afb703 | 2024-05-29 16:44:52 +0200 | [diff] [blame] | 129 | dependencies = dependencies_of_setting(cfg, setting) |
| 130 | if dependencies: |
| 131 | yield [dependencies], '' |
Gilles Peskine | c79ecea | 2024-05-23 16:32:39 +0200 | [diff] [blame] | 132 | return |
Gilles Peskine | 693611e | 2024-06-11 19:32:22 +0200 | [diff] [blame] | 133 | yield [], '' |
| 134 | |
| 135 | |
Gilles Peskine | 5454a84 | 2024-05-29 16:37:38 +0200 | [diff] [blame] | 136 | def enumerate_boolean_setting_cases(cfg: config.Config |
Gilles Peskine | 693611e | 2024-06-11 19:32:22 +0200 | [diff] [blame] | 137 | ) -> Iterable[test_case.TestCase]: |
Gilles Peskine | 5454a84 | 2024-05-29 16:37:38 +0200 | [diff] [blame] | 138 | """Emit test cases for all boolean settings.""" |
Gilles Peskine | 693611e | 2024-06-11 19:32:22 +0200 | [diff] [blame] | 139 | for name in sorted(cfg.settings.keys()): |
| 140 | setting = cfg.settings[name] |
| 141 | if not name.startswith('PSA_WANT_') and setting.value: |
| 142 | continue # non-boolean setting |
| 143 | for when_on in True, False: |
Gilles Peskine | 5454a84 | 2024-05-29 16:37:38 +0200 | [diff] [blame] | 144 | for deps, note in conditions_for_setting(cfg, setting): |
| 145 | yield single_setting_case(setting, when_on, deps, note) |
Gilles Peskine | 693611e | 2024-06-11 19:32:22 +0200 | [diff] [blame] | 146 | |
| 147 | |
| 148 | |
| 149 | class ConfigTestGenerator(test_data_generation.TestGenerator): |
| 150 | """Generate test cases for configuration reporting.""" |
| 151 | |
Gilles Peskine | 5454a84 | 2024-05-29 16:37:38 +0200 | [diff] [blame] | 152 | def __init__(self, settings): |
Gilles Peskine | 693611e | 2024-06-11 19:32:22 +0200 | [diff] [blame] | 153 | self.mbedtls_config = config.ConfigFile() |
| 154 | self.targets['test_suite_config.mbedtls_boolean'] = \ |
Gilles Peskine | 5454a84 | 2024-05-29 16:37:38 +0200 | [diff] [blame] | 155 | lambda: enumerate_boolean_setting_cases(self.mbedtls_config) |
Gilles Peskine | 693611e | 2024-06-11 19:32:22 +0200 | [diff] [blame] | 156 | self.psa_config = config.ConfigFile('include/psa/crypto_config.h') |
| 157 | self.targets['test_suite_config.psa_boolean'] = \ |
Gilles Peskine | 5454a84 | 2024-05-29 16:37:38 +0200 | [diff] [blame] | 158 | lambda: enumerate_boolean_setting_cases(self.psa_config) |
| 159 | super().__init__(settings) |
Gilles Peskine | 693611e | 2024-06-11 19:32:22 +0200 | [diff] [blame] | 160 | |
| 161 | |
| 162 | if __name__ == '__main__': |
| 163 | test_data_generation.main(sys.argv[1:], __doc__, ConfigTestGenerator) |