diff --git a/framework b/framework index 071831e25..8c488b1b8 160000 --- a/framework +++ b/framework @@ -1 +1 @@ -Subproject commit 071831e25bd336baa58bbdf65e985283f56e1b86 +Subproject commit 8c488b1b8f86384450c922f22cd1bee0b996be13 diff --git a/scripts/config.py b/scripts/config.py index 150078a69..50889fc9a 100755 --- a/scripts/config.py +++ b/scripts/config.py @@ -1,171 +1,23 @@ #!/usr/bin/env python3 -"""Mbed TLS configuration file manipulation library and tool +"""Mbed TLS and PSA configuration file manipulation library and tool Basic usage, to read the Mbed TLS configuration: - config = ConfigFile() + config = CombinedConfigFile() if 'MBEDTLS_RSA_C' in config: print('RSA is enabled') """ -# Note that as long as Mbed TLS 2.28 LTS is maintained, the version of -# this script in the mbedtls-2.28 branch must remain compatible with -# Python 3.4. The version in development may only use more recent features -# in parts that are not backported to 2.28. - ## Copyright The Mbed TLS Contributors ## SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later ## import os import re +import sys -from abc import ABCMeta +import framework_scripts_path # pylint: disable=unused-import +from mbedtls_framework import config_common -class Setting: - """Representation of one Mbed TLS mbedtls_config.h setting. - - Fields: - * name: the symbol name ('MBEDTLS_xxx'). - * value: the value of the macro. The empty string for a plain #define - with no value. - * active: True if name is defined, False if a #define for name is - present in mbedtls_config.h but commented out. - * section: the name of the section that contains this symbol. - """ - # pylint: disable=too-few-public-methods, too-many-arguments - def __init__(self, active, name, value='', section=None, configfile=None): - self.active = active - self.name = name - self.value = value - self.section = section - self.configfile = configfile - -class Config: - """Representation of the Mbed TLS configuration. - - In the documentation of this class, a symbol is said to be *active* - if there is a #define for it that is not commented out, and *known* - if there is a #define for it whether commented out or not. - - This class supports the following protocols: - * `name in config` is `True` if the symbol `name` is active, `False` - otherwise (whether `name` is inactive or not known). - * `config[name]` is the value of the macro `name`. If `name` is inactive, - raise `KeyError` (even if `name` is known). - * `config[name] = value` sets the value associated to `name`. `name` - must be known, but does not need to be set. This does not cause - name to become set. - """ - - # pylint: disable=unused-argument - def __init__(self): - self.settings = {} - - def __contains__(self, name): - """True if the given symbol is active (i.e. set). - - False if the given symbol is not set, even if a definition - is present but commented out. - """ - return name in self.settings and self.settings[name].active - - def all(self, *names): - """True if all the elements of names are active (i.e. set).""" - return all(self.__contains__(name) for name in names) - - def any(self, *names): - """True if at least one symbol in names are active (i.e. set).""" - return any(self.__contains__(name) for name in names) - - def known(self, name): - """True if a #define for name is present, whether it's commented out or not.""" - return name in self.settings - - def __getitem__(self, name): - """Get the value of name, i.e. what the preprocessor symbol expands to. - - If name is not known, raise KeyError. name does not need to be active. - """ - return self.settings[name].value - - def get(self, name, default=None): - """Get the value of name. If name is inactive (not set), return default. - - If a #define for name is present and not commented out, return - its expansion, even if this is the empty string. - - If a #define for name is present but commented out, return default. - """ - if name in self.settings: - return self.settings[name].value - else: - return default - - def __setitem__(self, name, value): - """If name is known, set its value. - - If name is not known, raise KeyError. - """ - self.settings[name].value = value - - def set(self, name, value=None): - """Set name to the given value and make it active. - - If value is None and name is already known, don't change its value. - If value is None and name is not known, set its value to the empty - string. - """ - if name in self.settings: - if value is not None: - self.settings[name].value = value - self.settings[name].active = True - else: - self.settings[name] = Setting(True, name, value=value) - - def unset(self, name): - """Make name unset (inactive). - - name remains known if it was known before. - """ - if name not in self.settings: - return - - setting = self.settings[name] - # Check if modifying the config file - if setting.configfile and setting.active: - setting.configfile.modified = True - - setting.active = False - - def adapt(self, adapter): - """Run adapter on each known symbol and (de)activate it accordingly. - - `adapter` must be a function that returns a boolean. It is called as - `adapter(name, active, section)` for each setting, where `active` is - `True` if `name` is set and `False` if `name` is known but unset, - and `section` is the name of the section containing `name`. If - `adapter` returns `True`, then set `name` (i.e. make it active), - otherwise unset `name` (i.e. make it known but inactive). - """ - for setting in self.settings.values(): - is_active = setting.active - setting.active = adapter(setting.name, setting.active, - setting.section) - # Check if modifying the config file - if setting.configfile and setting.active != is_active: - setting.configfile.modified = True - - def change_matching(self, regexs, enable): - """Change all symbols matching one of the regexs to the desired state.""" - if not regexs: - return - regex = re.compile('|'.join(regexs)) - for setting in self.settings.values(): - if regex.search(setting.name): - # Check if modifying the config file - if setting.configfile and setting.active != enable: - setting.configfile.modified = True - setting.active = enable def is_full_section(section): """Is this section affected by "config.py full" and friends? @@ -415,136 +267,8 @@ def no_platform_adapter(adapter): return adapter(name, active, section) return continuation -class ConfigFile(metaclass=ABCMeta): - """Representation of a configuration file.""" - def __init__(self, default_path, name, filename=None): - """Check if the config file exists.""" - if filename is None: - for candidate in default_path: - if os.path.lexists(candidate): - filename = candidate - break - else: - raise FileNotFoundError(f'{name} configuration file not found: ' - f'{filename if filename else default_path}') - - self.filename = filename - self.templates = [] - self.current_section = None - self.inclusion_guard = None - self.modified = False - - _define_line_regexp = (r'(?P\s*)' + - r'(?P(//\s*)?)' + - r'(?P#\s*define\s+)' + - r'(?P\w+)' + - r'(?P(?:\((?:\w|\s|,)*\))?)' + - r'(?P\s*)' + - r'(?P.*)') - _ifndef_line_regexp = r'#ifndef (?P\w+)' - _section_line_regexp = (r'\s*/?\*+\s*[\\@]name\s+SECTION:\s*' + - r'(?P
.*)[ */]*') - _config_line_regexp = re.compile(r'|'.join([_define_line_regexp, - _ifndef_line_regexp, - _section_line_regexp])) - def _parse_line(self, line): - """Parse a line in the config file, save the templates representing the lines - and return the corresponding setting element. - """ - - line = line.rstrip('\r\n') - m = re.match(self._config_line_regexp, line) - if m is None: - self.templates.append(line) - return None - elif m.group('section'): - self.current_section = m.group('section') - self.templates.append(line) - return None - elif m.group('inclusion_guard') and self.inclusion_guard is None: - self.inclusion_guard = m.group('inclusion_guard') - self.templates.append(line) - return None - else: - active = not m.group('commented_out') - name = m.group('name') - value = m.group('value') - if name == self.inclusion_guard and value == '': - # The file double-inclusion guard is not an option. - self.templates.append(line) - return None - template = (name, - m.group('indentation'), - m.group('define') + name + - m.group('arguments') + m.group('separator')) - self.templates.append(template) - - return (active, name, value, self.current_section) - - def parse_file(self): - """Parse the whole file and return the settings.""" - - with open(self.filename, 'r', encoding='utf-8') as file: - for line in file: - setting = self._parse_line(line) - if setting is not None: - yield setting - self.current_section = None - - #pylint: disable=no-self-use - def _format_template(self, setting, indent, middle): - """Build a line for the config file for the given setting. - - The line has the form "#define " - where is "#define ". - """ - - value = setting.value - if value is None: - value = '' - # Normally the whitespace to separate the symbol name from the - # value is part of middle, and there's no whitespace for a symbol - # with no value. But if a symbol has been changed from having a - # value to not having one, the whitespace is wrong, so fix it. - if value: - if middle[-1] not in '\t ': - middle += ' ' - else: - middle = middle.rstrip() - return ''.join([indent, - '' if setting.active else '//', - middle, - value]).rstrip() - - def write_to_stream(self, settings, output): - """Write the whole configuration to output.""" - - for template in self.templates: - if isinstance(template, str): - line = template - else: - name, indent, middle = template - line = self._format_template(settings[name], indent, middle) - output.write(line + '\n') - - def write(self, settings, filename=None): - """Write the whole configuration to the file it was read from. - - If filename is specified, write to this file instead. - """ - - if filename is None: - filename = self.filename - - # Not modified so no need to write to the file - if not self.modified and filename == self.filename: - return - - with open(filename, 'w', encoding='utf-8') as output: - self.write_to_stream(settings, output) - -class MbedTLSConfigFile(ConfigFile): +class MbedTLSConfigFile(config_common.ConfigFile): """Representation of an MbedTLS configuration file.""" _path_in_tree = 'include/mbedtls/mbedtls_config.h' @@ -559,15 +283,18 @@ class MbedTLSConfigFile(ConfigFile): super().__init__(self.default_path, 'Mbed TLS', filename) self.current_section = 'header' -class CryptoConfigFile(ConfigFile): + +class CryptoConfigFile(config_common.ConfigFile): """Representation of a Crypto configuration file.""" # Temporary, while Mbed TLS does not just rely on the TF-PSA-Crypto # build system to build its crypto library. When it does, the # condition can just be removed. - _path_in_tree = 'include/psa/crypto_config.h' \ - if os.path.isfile('include/psa/crypto_config.h') else \ - 'tf-psa-crypto/include/psa/crypto_config.h' + _path_in_tree = ('include/psa/crypto_config.h' + if not os.path.isdir(os.path.join(os.path.dirname(__file__), + os.pardir, + 'tf-psa-crypto')) else + 'tf-psa-crypto/include/psa/crypto_config.h') default_path = [_path_in_tree, os.path.join(os.path.dirname(__file__), os.pardir, @@ -578,7 +305,8 @@ class CryptoConfigFile(ConfigFile): def __init__(self, filename=None): super().__init__(self.default_path, 'Crypto', filename) -class MbedTLSConfig(Config): + +class MbedTLSConfig(config_common.Config): """Representation of the Mbed TLS configuration. See the documentation of the `Config` class for methods to query @@ -589,33 +317,22 @@ class MbedTLSConfig(Config): """Read the Mbed TLS configuration file.""" super().__init__() - self.configfile = MbedTLSConfigFile(filename) - self.settings.update({name: Setting(active, name, value, section, self.configfile) + configfile = MbedTLSConfigFile(filename) + self.configfiles.append(configfile) + self.settings.update({name: config_common.Setting(configfile, active, name, value, section) for (active, name, value, section) - in self.configfile.parse_file()}) + in configfile.parse_file()}) def set(self, name, value=None): """Set name to the given value and make it active.""" if name not in self.settings: - self.configfile.templates.append((name, '', '#define ' + name + ' ')) + self._get_configfile().templates.append((name, '', '#define ' + name + ' ')) super().set(name, value) - def write(self, filename=None): - """Write the whole configuration to the file it was read from. - If filename is specified, write to this file instead. - """ - - self.configfile.write(self.settings, filename) - - def filename(self): - """Get the name of the config file.""" - - return self.configfile.filename - -class CryptoConfig(Config): +class CryptoConfig(config_common.Config): """Representation of the PSA crypto configuration. See the documentation of the `Config` class for methods to query @@ -626,10 +343,11 @@ class CryptoConfig(Config): """Read the PSA crypto configuration file.""" super().__init__() - self.configfile = CryptoConfigFile(filename) - self.settings.update({name: Setting(active, name, value, section, self.configfile) + configfile = CryptoConfigFile(filename) + self.configfiles.append(configfile) + self.settings.update({name: config_common.Setting(configfile, active, name, value, section) for (active, name, value, section) - in self.configfile.parse_file()}) + in configfile.parse_file()}) def set(self, name, value='1'): """Set name to the given value and make it active.""" @@ -640,24 +358,12 @@ class CryptoConfig(Config): raise ValueError(f'Feature is unstable: \'{name}\'') if name not in self.settings: - self.configfile.templates.append((name, '', '#define ' + name + ' ')) + self._get_configfile().templates.append((name, '', '#define ' + name + ' ')) super().set(name, value) - def write(self, filename=None): - """Write the whole configuration to the file it was read from. - If filename is specified, write to this file instead. - """ - - self.configfile.write(self.settings, filename) - - def filename(self): - """Get the name of the config file.""" - - return self.configfile.filename - -class CombinedConfig(Config): +class CombinedConfig(config_common.Config): """Representation of MbedTLS and PSA crypto configuration See the documentation of the `Config` class for methods to query @@ -673,13 +379,14 @@ class CombinedConfig(Config): self.crypto_configfile = config else: raise ValueError(f'Invalid configfile: {config}') + self.configfiles.append(config) - self.settings.update({name: Setting(active, name, value, section, configfile) + self.settings.update({name: config_common.Setting(configfile, active, name, value, section) for configfile in [self.mbedtls_configfile, self.crypto_configfile] for (active, name, value, section) in configfile.parse_file()}) _crypto_regexp = re.compile(r'$PSA_.*') - def _get_configfile(self, name): + def _get_configfile(self, name=None): """Find a config type for a setting name""" if name in self.settings: @@ -689,10 +396,6 @@ class CombinedConfig(Config): else: return self.mbedtls_configfile - def __setitem__(self, name, value): - super().__setitem__(name, value) - self.settings[name].configfile.modified = True - def set(self, name, value=None): """Set name to the given value and make it active.""" @@ -708,28 +411,27 @@ class CombinedConfig(Config): if not value: value = '1' - if name in self.settings: - setting = self.settings[name] - if not setting.active or (value is not None and setting.value != value): - configfile.modified = True - else: + if name not in self.settings: configfile.templates.append((name, '', '#define ' + name + ' ')) - configfile.modified = True super().set(name, value) + #pylint: disable=arguments-differ def write(self, mbedtls_file=None, crypto_file=None): """Write the whole configuration to the file it was read from. If mbedtls_file or crypto_file is specified, write the specific configuration to the corresponding file instead. + + Two file name parameters and not only one as in the super class as we handle + two configuration files in this class. """ self.mbedtls_configfile.write(self.settings, mbedtls_file) self.crypto_configfile.write(self.settings, crypto_file) def filename(self, name=None): - """Get the names of the config files. + """Get the name of the config files. If 'name' is specified return the name of the config file where it is defined. """ @@ -739,128 +441,62 @@ class CombinedConfig(Config): return self._get_configfile(name).filename + +class MbedTLSConfigTool(config_common.ConfigTool): + """Command line mbedtls_config.h and crypto_config.h manipulation tool.""" + + def __init__(self): + super().__init__(MbedTLSConfigFile.default_path) + self.config = CombinedConfig(MbedTLSConfigFile(self.args.file), + CryptoConfigFile(self.args.cryptofile)) + + def custom_parser_options(self): + """Adds MbedTLS specific options for the parser.""" + + self.parser.add_argument( + '--cryptofile', '-c', + help="""Crypto file to read (and modify if requested). Default: {}.""" + .format(CryptoConfigFile.default_path)) + + self.add_adapter( + 'baremetal', baremetal_adapter, + """Like full, but exclude features that require platform features + such as file input-output. + """) + self.add_adapter( + 'baremetal_size', baremetal_size_adapter, + """Like baremetal, but exclude debugging features. Useful for code size measurements. + """) + self.add_adapter( + 'full', full_adapter, + """Uncomment most features. + Exclude alternative implementations and platform support options, as well as + some options that are awkward to test. + """) + self.add_adapter( + 'full_no_deprecated', no_deprecated_adapter(full_adapter), + """Uncomment most non-deprecated features. + Like "full", but without deprecated features. + """) + self.add_adapter( + 'full_no_platform', no_platform_adapter(full_adapter), + """Uncomment most non-platform features. Like "full", but without platform features. + """) + self.add_adapter( + 'realfull', realfull_adapter, + """Uncomment all boolean #defines. + Suitable for generating documentation, but not for building. + """) + self.add_adapter( + 'crypto', crypto_adapter(None), + """Only include crypto features. Exclude X.509 and TLS.""") + self.add_adapter( + 'crypto_baremetal', crypto_adapter(baremetal_adapter), + """Like baremetal, but with only crypto features, excluding X.509 and TLS.""") + self.add_adapter( + 'crypto_full', crypto_adapter(full_adapter), + """Like full, but with only crypto features, excluding X.509 and TLS.""") + + if __name__ == '__main__': - #pylint: disable=too-many-statements - def main(): - """Command line mbedtls_config.h manipulation tool.""" - parser = argparse.ArgumentParser(description=""" - Mbed TLS configuration file manipulation tool. - """) - parser.add_argument('--file', '-f', - help="""File to read (and modify if requested). - Default: {}. - """.format(MbedTLSConfigFile.default_path)) - parser.add_argument('--cryptofile', '-c', - help="""Crypto file to read (and modify if requested). - Default: {}. - """.format(CryptoConfigFile.default_path)) - parser.add_argument('--force', '-o', - action='store_true', - help="""For the set command, if SYMBOL is not - present, add a definition for it.""") - parser.add_argument('--write', '-w', metavar='FILE', - help="""File to write to instead of the input file.""") - subparsers = parser.add_subparsers(dest='command', - title='Commands') - parser_get = subparsers.add_parser('get', - help="""Find the value of SYMBOL - and print it. Exit with - status 0 if a #define for SYMBOL is - found, 1 otherwise. - """) - parser_get.add_argument('symbol', metavar='SYMBOL') - parser_set = subparsers.add_parser('set', - help="""Set SYMBOL to VALUE. - If VALUE is omitted, just uncomment - the #define for SYMBOL. - Error out of a line defining - SYMBOL (commented or not) is not - found, unless --force is passed. - """) - parser_set.add_argument('symbol', metavar='SYMBOL') - parser_set.add_argument('value', metavar='VALUE', nargs='?', - default='') - parser_set_all = subparsers.add_parser('set-all', - help="""Uncomment all #define - whose name contains a match for - REGEX.""") - parser_set_all.add_argument('regexs', metavar='REGEX', nargs='*') - parser_unset = subparsers.add_parser('unset', - help="""Comment out the #define - for SYMBOL. Do nothing if none - is present.""") - parser_unset.add_argument('symbol', metavar='SYMBOL') - parser_unset_all = subparsers.add_parser('unset-all', - help="""Comment out all #define - whose name contains a match for - REGEX.""") - parser_unset_all.add_argument('regexs', metavar='REGEX', nargs='*') - - def add_adapter(name, function, description): - subparser = subparsers.add_parser(name, help=description) - subparser.set_defaults(adapter=function) - add_adapter('baremetal', baremetal_adapter, - """Like full, but exclude features that require platform - features such as file input-output.""") - add_adapter('baremetal_size', baremetal_size_adapter, - """Like baremetal, but exclude debugging features. - Useful for code size measurements.""") - add_adapter('full', full_adapter, - """Uncomment most features. - Exclude alternative implementations and platform support - options, as well as some options that are awkward to test. - """) - add_adapter('full_no_deprecated', no_deprecated_adapter(full_adapter), - """Uncomment most non-deprecated features. - Like "full", but without deprecated features. - """) - add_adapter('full_no_platform', no_platform_adapter(full_adapter), - """Uncomment most non-platform features. - Like "full", but without platform features. - """) - add_adapter('realfull', realfull_adapter, - """Uncomment all boolean #defines. - Suitable for generating documentation, but not for building.""") - add_adapter('crypto', crypto_adapter(None), - """Only include crypto features. Exclude X.509 and TLS.""") - add_adapter('crypto_baremetal', crypto_adapter(baremetal_adapter), - """Like baremetal, but with only crypto features, - excluding X.509 and TLS.""") - add_adapter('crypto_full', crypto_adapter(full_adapter), - """Like full, but with only crypto features, - excluding X.509 and TLS.""") - - args = parser.parse_args() - config = CombinedConfig(MbedTLSConfigFile(args.file), CryptoConfigFile(args.cryptofile)) - if args.command is None: - parser.print_help() - return 1 - elif args.command == 'get': - if args.symbol in config: - value = config[args.symbol] - if value: - sys.stdout.write(value + '\n') - return 0 if args.symbol in config else 1 - elif args.command == 'set': - if not args.force and args.symbol not in config.settings: - sys.stderr.write("A #define for the symbol {} " - "was not found in {}\n" - .format(args.symbol, config.filename(args.symbol))) - return 1 - config.set(args.symbol, value=args.value) - elif args.command == 'set-all': - config.change_matching(args.regexs, True) - elif args.command == 'unset': - config.unset(args.symbol) - elif args.command == 'unset-all': - config.change_matching(args.regexs, False) - else: - config.adapt(args.adapter) - config.write(args.write) - return 0 - - # Import modules only used by main only if main is defined and called. - # pylint: disable=wrong-import-position - import argparse - import sys - sys.exit(main()) + sys.exit(MbedTLSConfigTool().main())