blob: ca277795c7d775720bfad7d28cd8be8b1716aa09 [file] [log] [blame]
Gilles Peskinee7c44552021-01-25 21:40:45 +01001"""Collect macro definitions from header files.
2"""
3
4# Copyright The Mbed TLS Contributors
5# SPDX-License-Identifier: Apache-2.0
6#
7# Licensed under the Apache License, Version 2.0 (the "License"); you may
8# not use this file except in compliance with the License.
9# You may obtain a copy of the License at
10#
11# http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16# See the License for the specific language governing permissions and
17# limitations under the License.
18
19import re
20
21class PSAMacroCollector:
22 """Collect PSA crypto macro definitions from C header files.
23 """
24
Gilles Peskine13d60eb2021-01-25 22:42:14 +010025 def __init__(self, include_intermediate=False):
26 """Set up an object to collect PSA macro definitions.
27
28 Call the read_file method of the constructed object on each header file.
29
30 * include_intermediate: if true, include intermediate macros such as
31 PSA_XXX_BASE that do not designate semantic values.
32 """
33 self.include_intermediate = include_intermediate
Gilles Peskinee7c44552021-01-25 21:40:45 +010034 self.statuses = set()
35 self.key_types = set()
36 self.key_types_from_curve = {}
37 self.key_types_from_group = {}
38 self.ecc_curves = set()
39 self.dh_groups = set()
40 self.algorithms = set()
41 self.hash_algorithms = set()
42 self.ka_algorithms = set()
43 self.algorithms_from_hash = {}
44 self.key_usages = set()
45
Gilles Peskinef8deb752021-01-25 22:41:45 +010046 def is_internal_name(self, name):
47 """Whether this is an internal macro. Internal macros will be skipped."""
Gilles Peskine13d60eb2021-01-25 22:42:14 +010048 if not self.include_intermediate:
49 if name.endswith('_BASE') or name.endswith('_NONE'):
50 return True
51 if '_CATEGORY_' in name:
52 return True
Gilles Peskinef8deb752021-01-25 22:41:45 +010053 return name.endswith('_FLAG') or name.endswith('MASK')
54
Gilles Peskinee7c44552021-01-25 21:40:45 +010055 # "#define" followed by a macro name with either no parameters
56 # or a single parameter and a non-empty expansion.
57 # Grab the macro name in group 1, the parameter name if any in group 2
58 # and the expansion in group 3.
59 _define_directive_re = re.compile(r'\s*#\s*define\s+(\w+)' +
60 r'(?:\s+|\((\w+)\)\s*)' +
61 r'(.+)')
62 _deprecated_definition_re = re.compile(r'\s*MBEDTLS_DEPRECATED')
63
64 def read_line(self, line):
65 """Parse a C header line and record the PSA identifier it defines if any.
66 This function analyzes lines that start with "#define PSA_"
67 (up to non-significant whitespace) and skips all non-matching lines.
68 """
69 # pylint: disable=too-many-branches
70 m = re.match(self._define_directive_re, line)
71 if not m:
72 return
73 name, parameter, expansion = m.groups()
74 expansion = re.sub(r'/\*.*?\*/|//.*', r' ', expansion)
75 if re.match(self._deprecated_definition_re, expansion):
76 # Skip deprecated values, which are assumed to be
77 # backward compatibility aliases that share
78 # numerical values with non-deprecated values.
79 return
Gilles Peskinef8deb752021-01-25 22:41:45 +010080 if self.is_internal_name(name):
Gilles Peskinee7c44552021-01-25 21:40:45 +010081 # Macro only to build actual values
82 return
83 elif (name.startswith('PSA_ERROR_') or name == 'PSA_SUCCESS') \
84 and not parameter:
85 self.statuses.add(name)
86 elif name.startswith('PSA_KEY_TYPE_') and not parameter:
87 self.key_types.add(name)
88 elif name.startswith('PSA_KEY_TYPE_') and parameter == 'curve':
89 self.key_types_from_curve[name] = name[:13] + 'IS_' + name[13:]
90 elif name.startswith('PSA_KEY_TYPE_') and parameter == 'group':
91 self.key_types_from_group[name] = name[:13] + 'IS_' + name[13:]
92 elif name.startswith('PSA_ECC_FAMILY_') and not parameter:
93 self.ecc_curves.add(name)
94 elif name.startswith('PSA_DH_FAMILY_') and not parameter:
95 self.dh_groups.add(name)
96 elif name.startswith('PSA_ALG_') and not parameter:
97 if name in ['PSA_ALG_ECDSA_BASE',
98 'PSA_ALG_RSA_PKCS1V15_SIGN_BASE']:
99 # Ad hoc skipping of duplicate names for some numerical values
100 return
101 self.algorithms.add(name)
102 # Ad hoc detection of hash algorithms
103 if re.search(r'0x020000[0-9A-Fa-f]{2}', expansion):
104 self.hash_algorithms.add(name)
105 # Ad hoc detection of key agreement algorithms
106 if re.search(r'0x09[0-9A-Fa-f]{2}0000', expansion):
107 self.ka_algorithms.add(name)
108 elif name.startswith('PSA_ALG_') and parameter == 'hash_alg':
109 if name in ['PSA_ALG_DSA', 'PSA_ALG_ECDSA']:
110 # A naming irregularity
111 tester = name[:8] + 'IS_RANDOMIZED_' + name[8:]
112 else:
113 tester = name[:8] + 'IS_' + name[8:]
114 self.algorithms_from_hash[name] = tester
115 elif name.startswith('PSA_KEY_USAGE_') and not parameter:
116 self.key_usages.add(name)
117 else:
118 # Other macro without parameter
119 return
120
121 _nonascii_re = re.compile(rb'[^\x00-\x7f]+')
122 _continued_line_re = re.compile(rb'\\\r?\n\Z')
123 def read_file(self, header_file):
124 for line in header_file:
125 m = re.search(self._continued_line_re, line)
126 while m:
127 cont = next(header_file)
128 line = line[:m.start(0)] + cont
129 m = re.search(self._continued_line_re, line)
130 line = re.sub(self._nonascii_re, rb'', line).decode('ascii')
131 self.read_line(line)