blob: 8bbb711a6c65aa94bb0ec6c8e730867f515fa951 [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 Peskine2d2e9242021-01-12 00:52:31 +010025def is_systematic_dependency(dep):
26 """Whether dep is a PSA dependency which is determined systematically."""
27 return dep.startswith('PSA_WANT_')
28
29def dependencies_of_symbol(symbol):
30 """Return the dependencies for a symbol that designates a cryptographic mechanism."""
31 return {symbol.replace('_', '_WANT_', 1)}
32
33def systematic_dependencies(file_name, function_name, arguments):
34 #pylint: disable=unused-argument
35 """List the systematically determined dependency for a test case."""
36 deps = set()
37 for arg in arguments:
38 for symbol in re.findall(r'PSA_(?:ALG|KEY_TYPE)_\w+', arg):
39 deps.update(dependencies_of_symbol(symbol))
40 return sorted(deps)
41
Gilles Peskine82ebaa42021-01-12 00:45:14 +010042def updated_dependencies(file_name, function_name, arguments, dependencies):
43 """Rework the list of dependencies into PSA_WANT_xxx.
44
45 Remove classic crypto dependencies such as MBEDTLS_RSA_C,
46 MBEDTLS_PKCS1_V15, etc.
47
48 Add systematic PSA_WANT_xxx dependencies based on the called function and
49 its arguments, replacing existing PSA_WANT_xxx dependencies.
50 """
Gilles Peskine2d2e9242021-01-12 00:52:31 +010051 automatic = systematic_dependencies(file_name, function_name, arguments)
52 manual = [dep for dep in dependencies
53 if not is_systematic_dependency(dep)]
54 return automatic + manual
Gilles Peskine82ebaa42021-01-12 00:45:14 +010055
Gilles Peskine45e9e732021-01-12 00:47:03 +010056def keep_manual_dependencies(file_name, function_name, arguments):
57 #pylint: disable=unused-argument
58 """Declare test functions with unusual dependencies here."""
59 return False
60
Gilles Peskinebdffaea2021-01-12 00:37:38 +010061def process_data_stanza(stanza, file_name, test_case_number):
62 """Update PSA crypto dependencies in one Mbed TLS test case.
63
64 stanza is the test case text (including the description, the dependencies,
65 the line with the function and arguments, and optionally comments). Return
66 a new stanza with an updated dependency line, preserving everything else
67 (description, comments, arguments, etc.).
68 """
Gilles Peskine82ebaa42021-01-12 00:45:14 +010069 if not stanza.lstrip('\n'):
70 # Just blank lines
71 return stanza
72 # Expect 2 or 3 non-comment lines: description, optional dependencies,
73 # function-and-arguments.
74 content_matches = list(re.finditer(r'^[\t ]*([^\t #].*)$', stanza, re.M))
75 if len(content_matches) < 2:
76 raise Exception('Not enough content lines in paragraph {} in {}'
77 .format(test_case_number, file_name))
78 if len(content_matches) > 3:
79 raise Exception('Too many content lines in paragraph {} in {}'
80 .format(test_case_number, file_name))
81 arguments = content_matches[-1].group(0).split(':')
82 function_name = arguments.pop(0)
Gilles Peskine45e9e732021-01-12 00:47:03 +010083 if keep_manual_dependencies(file_name, function_name, arguments):
84 return stanza
Gilles Peskine82ebaa42021-01-12 00:45:14 +010085 if len(content_matches) == 2:
86 # Insert a line for the dependencies. If it turns out that there are
87 # no dependencies, we'll remove that empty line below.
88 dependencies_location = content_matches[-1].start()
89 text_before = stanza[:dependencies_location]
90 text_after = '\n' + stanza[dependencies_location:]
91 old_dependencies = []
92 dependencies_leader = 'depends_on:'
93 else:
94 dependencies_match = content_matches[-2]
95 text_before = stanza[:dependencies_match.start()]
96 text_after = stanza[dependencies_match.end():]
97 old_dependencies = dependencies_match.group(0).split(':')
98 dependencies_leader = old_dependencies.pop(0) + ':'
99 if dependencies_leader != 'depends_on:':
100 raise Exception('Next-to-last line does not start with "depends_on:"'
101 ' in paragraph {} in {}'
102 .format(test_case_number, file_name))
103 new_dependencies = updated_dependencies(file_name, function_name, arguments,
104 old_dependencies)
105 if new_dependencies:
106 stanza = (text_before +
107 dependencies_leader + ':'.join(new_dependencies) +
108 text_after)
109 else:
110 # The dependencies have become empty. Remove the depends_on: line.
111 assert text_after[0] == '\n'
112 stanza = text_before + text_after[1:]
Gilles Peskinebdffaea2021-01-12 00:37:38 +0100113 return stanza
114
115def process_data_file(file_name, old_content):
116 """Update PSA crypto dependencies in an Mbed TLS test suite data file.
117
118 Process old_content (the old content of the file) and return the new content.
119 """
120 old_stanzas = old_content.split('\n\n')
121 new_stanzas = [process_data_stanza(stanza, file_name, n)
122 for n, stanza in enumerate(old_stanzas, start=1)]
123 return '\n\n'.join(new_stanzas)
124
125def update_file(file_name, old_content, new_content):
126 """Update the given file with the given new content.
127
128 Replace the existing file. The previous version is renamed to *.bak.
129 Don't modify the file if the content was unchanged.
130 """
131 if new_content == old_content:
132 return
133 backup = file_name + '.bak'
134 tmp = file_name + '.tmp'
135 with open(tmp, 'w', encoding='utf-8') as new_file:
136 new_file.write(new_content)
137 os.replace(file_name, backup)
138 os.replace(tmp, file_name)
139
140def process_file(file_name):
141 """Update PSA crypto dependencies in an Mbed TLS test suite data file.
142
143 Replace the existing file. The previous version is renamed to *.bak.
144 Don't modify the file if the content was unchanged.
145 """
146 old_content = open(file_name, encoding='utf-8').read()
147 if file_name.endswith('.data'):
148 new_content = process_data_file(file_name, old_content)
149 else:
150 raise Exception('File type not recognized: {}'
151 .format(file_name))
152 update_file(file_name, old_content, new_content)
153
154def main(args):
155 for file_name in args:
156 process_file(file_name)
157
158if __name__ == '__main__':
159 main(sys.argv[1:])