blob: 8deff8226c71dc42d9e04d3ca1626e323fabd196 [file] [log] [blame]
Gilles Peskinebdffaea2021-01-12 00:37:38 +01001#!/usr/bin/env python3
2
3"""Edit test cases to use PSA dependencies instead of classic dependencies.
4"""
5
6# Copyright The Mbed TLS Contributors
7# SPDX-License-Identifier: Apache-2.0
8#
9# Licensed under the Apache License, Version 2.0 (the "License"); you may
10# not use this file except in compliance with the License.
11# You may obtain a copy of the License at
12#
13# http://www.apache.org/licenses/LICENSE-2.0
14#
15# Unless required by applicable law or agreed to in writing, software
16# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
17# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18# See the License for the specific language governing permissions and
19# limitations under the License.
20
21import os
Gilles Peskine82ebaa42021-01-12 00:45:14 +010022import re
Gilles Peskinebdffaea2021-01-12 00:37:38 +010023import sys
24
Gilles Peskine9bbba5e2021-01-12 00:55:55 +010025CLASSIC_DEPENDENCIES = frozenset([
26])
27
28def is_classic_dependency(dep):
29 """Whether dep is a classic dependency that PSA test cases should not use."""
30 if dep.startswith('!'):
31 dep = dep[1:]
32 return dep in CLASSIC_DEPENDENCIES
33
Gilles Peskine2d2e9242021-01-12 00:52:31 +010034def is_systematic_dependency(dep):
35 """Whether dep is a PSA dependency which is determined systematically."""
36 return dep.startswith('PSA_WANT_')
37
38def dependencies_of_symbol(symbol):
39 """Return the dependencies for a symbol that designates a cryptographic mechanism."""
40 return {symbol.replace('_', '_WANT_', 1)}
41
42def systematic_dependencies(file_name, function_name, arguments):
43 #pylint: disable=unused-argument
44 """List the systematically determined dependency for a test case."""
45 deps = set()
46 for arg in arguments:
47 for symbol in re.findall(r'PSA_(?:ALG|KEY_TYPE)_\w+', arg):
48 deps.update(dependencies_of_symbol(symbol))
49 return sorted(deps)
50
Gilles Peskine82ebaa42021-01-12 00:45:14 +010051def updated_dependencies(file_name, function_name, arguments, dependencies):
52 """Rework the list of dependencies into PSA_WANT_xxx.
53
54 Remove classic crypto dependencies such as MBEDTLS_RSA_C,
55 MBEDTLS_PKCS1_V15, etc.
56
57 Add systematic PSA_WANT_xxx dependencies based on the called function and
58 its arguments, replacing existing PSA_WANT_xxx dependencies.
59 """
Gilles Peskine2d2e9242021-01-12 00:52:31 +010060 automatic = systematic_dependencies(file_name, function_name, arguments)
61 manual = [dep for dep in dependencies
Gilles Peskine9bbba5e2021-01-12 00:55:55 +010062 if not (is_systematic_dependency(dep) or
63 is_classic_dependency(dep))]
Gilles Peskine2d2e9242021-01-12 00:52:31 +010064 return automatic + manual
Gilles Peskine82ebaa42021-01-12 00:45:14 +010065
Gilles Peskine45e9e732021-01-12 00:47:03 +010066def keep_manual_dependencies(file_name, function_name, arguments):
67 #pylint: disable=unused-argument
68 """Declare test functions with unusual dependencies here."""
69 return False
70
Gilles Peskinebdffaea2021-01-12 00:37:38 +010071def process_data_stanza(stanza, file_name, test_case_number):
72 """Update PSA crypto dependencies in one Mbed TLS test case.
73
74 stanza is the test case text (including the description, the dependencies,
75 the line with the function and arguments, and optionally comments). Return
76 a new stanza with an updated dependency line, preserving everything else
77 (description, comments, arguments, etc.).
78 """
Gilles Peskine82ebaa42021-01-12 00:45:14 +010079 if not stanza.lstrip('\n'):
80 # Just blank lines
81 return stanza
82 # Expect 2 or 3 non-comment lines: description, optional dependencies,
83 # function-and-arguments.
84 content_matches = list(re.finditer(r'^[\t ]*([^\t #].*)$', stanza, re.M))
85 if len(content_matches) < 2:
86 raise Exception('Not enough content lines in paragraph {} in {}'
87 .format(test_case_number, file_name))
88 if len(content_matches) > 3:
89 raise Exception('Too many content lines in paragraph {} in {}'
90 .format(test_case_number, file_name))
91 arguments = content_matches[-1].group(0).split(':')
92 function_name = arguments.pop(0)
Gilles Peskine45e9e732021-01-12 00:47:03 +010093 if keep_manual_dependencies(file_name, function_name, arguments):
94 return stanza
Gilles Peskine82ebaa42021-01-12 00:45:14 +010095 if len(content_matches) == 2:
96 # Insert a line for the dependencies. If it turns out that there are
97 # no dependencies, we'll remove that empty line below.
98 dependencies_location = content_matches[-1].start()
99 text_before = stanza[:dependencies_location]
100 text_after = '\n' + stanza[dependencies_location:]
101 old_dependencies = []
102 dependencies_leader = 'depends_on:'
103 else:
104 dependencies_match = content_matches[-2]
105 text_before = stanza[:dependencies_match.start()]
106 text_after = stanza[dependencies_match.end():]
107 old_dependencies = dependencies_match.group(0).split(':')
108 dependencies_leader = old_dependencies.pop(0) + ':'
109 if dependencies_leader != 'depends_on:':
110 raise Exception('Next-to-last line does not start with "depends_on:"'
111 ' in paragraph {} in {}'
112 .format(test_case_number, file_name))
113 new_dependencies = updated_dependencies(file_name, function_name, arguments,
114 old_dependencies)
115 if new_dependencies:
116 stanza = (text_before +
117 dependencies_leader + ':'.join(new_dependencies) +
118 text_after)
119 else:
120 # The dependencies have become empty. Remove the depends_on: line.
121 assert text_after[0] == '\n'
122 stanza = text_before + text_after[1:]
Gilles Peskinebdffaea2021-01-12 00:37:38 +0100123 return stanza
124
125def process_data_file(file_name, old_content):
126 """Update PSA crypto dependencies in an Mbed TLS test suite data file.
127
128 Process old_content (the old content of the file) and return the new content.
129 """
130 old_stanzas = old_content.split('\n\n')
131 new_stanzas = [process_data_stanza(stanza, file_name, n)
132 for n, stanza in enumerate(old_stanzas, start=1)]
133 return '\n\n'.join(new_stanzas)
134
135def update_file(file_name, old_content, new_content):
136 """Update the given file with the given new content.
137
138 Replace the existing file. The previous version is renamed to *.bak.
139 Don't modify the file if the content was unchanged.
140 """
141 if new_content == old_content:
142 return
143 backup = file_name + '.bak'
144 tmp = file_name + '.tmp'
145 with open(tmp, 'w', encoding='utf-8') as new_file:
146 new_file.write(new_content)
147 os.replace(file_name, backup)
148 os.replace(tmp, file_name)
149
150def process_file(file_name):
151 """Update PSA crypto dependencies in an Mbed TLS test suite data file.
152
153 Replace the existing file. The previous version is renamed to *.bak.
154 Don't modify the file if the content was unchanged.
155 """
156 old_content = open(file_name, encoding='utf-8').read()
157 if file_name.endswith('.data'):
158 new_content = process_data_file(file_name, old_content)
159 else:
160 raise Exception('File type not recognized: {}'
161 .format(file_name))
162 update_file(file_name, old_content, new_content)
163
164def main(args):
165 for file_name in args:
166 process_file(file_name)
167
168if __name__ == '__main__':
169 main(sys.argv[1:])