blob: 600d16096eaad3148ee6900b99b71e5fa4037e88 [file] [log] [blame]
Jerry Yue78ee992021-09-22 15:42:14 +08001#!/usr/bin/env python3
2
Dave Rodgman4914d502022-04-22 15:26:47 +01003"""Generate library/ssl_debug_helpers_generated.c
Jerry Yue78ee992021-09-22 15:42:14 +08004
5The code generated by this module includes debug helper functions that can not be
6implemented by fixed codes.
7
8"""
9
10# Copyright The Mbed TLS Contributors
Dave Rodgman16799db2023-11-02 19:47:20 +000011# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
Jerry Yue78ee992021-09-22 15:42:14 +080012import sys
13import re
14import os
15import textwrap
Jerry Yue6369b02021-12-02 13:51:26 +080016import argparse
David Horstmannb8e13322024-05-03 14:50:58 +010017
David Horstmann7f6c81a2024-05-10 16:58:31 +010018import framework_scripts_path # pylint: disable=unused-import
David Horstmann9638ca32024-05-03 14:36:12 +010019from mbedtls_framework import build_tree
Jerry Yue78ee992021-09-22 15:42:14 +080020
Jerry Yue6369b02021-12-02 13:51:26 +080021
Jerry Yue78ee992021-09-22 15:42:14 +080022def remove_c_comments(string):
23 """
24 Remove C style comments from input string
25 """
26 string_pattern = r"(?P<string>\".*?\"|\'.*?\')"
27 comment_pattern = r"(?P<comment>/\*.*?\*/|//[^\r\n]*$)"
28 pattern = re.compile(string_pattern + r'|' + comment_pattern,
Jerry Yue6369b02021-12-02 13:51:26 +080029 re.MULTILINE | re.DOTALL)
30
Jerry Yue78ee992021-09-22 15:42:14 +080031 def replacer(match):
32 if match.lastgroup == 'comment':
33 return ""
34 return match.group()
35 return pattern.sub(replacer, string)
36
Jerry Yue6369b02021-12-02 13:51:26 +080037
Jerry Yue78ee992021-09-22 15:42:14 +080038class CondDirectiveNotMatch(Exception):
39 pass
40
Jerry Yue6369b02021-12-02 13:51:26 +080041
Jerry Yue988f0f2021-11-11 13:22:20 +080042def preprocess_c_source_code(source, *classes):
Jerry Yue78ee992021-09-22 15:42:14 +080043 """
44 Simple preprocessor for C source code.
45
Shaun Case8b0ecbc2021-12-20 21:14:10 -080046 Only processes condition directives without expanding them.
Jerry Yu6389b252021-12-02 10:28:40 +080047 Yield object according to the classes input. Most match firstly
Jerry Yue78ee992021-09-22 15:42:14 +080048
Jerry Yu6389b252021-12-02 10:28:40 +080049 If the directive pair does not match , raise CondDirectiveNotMatch.
Jerry Yue78ee992021-09-22 15:42:14 +080050
51 Assume source code does not include comments and compile pass.
52
53 """
54
55 pattern = re.compile(r"^[ \t]*#[ \t]*" +
56 r"(?P<directive>(if[ \t]|ifndef[ \t]|ifdef[ \t]|else|endif))" +
57 r"[ \t]*(?P<param>(.*\\\n)*.*$)",
58 re.MULTILINE)
59 stack = []
60
61 def _yield_objects(s, d, p, st, end):
62 """
63 Output matched source piece
64 """
65 nonlocal stack
66 start_line, end_line = '', ''
67 if stack:
68 start_line = '#{} {}'.format(d, p)
69 if d == 'if':
70 end_line = '#endif /* {} */'.format(p)
71 elif d == 'ifdef':
72 end_line = '#endif /* defined({}) */'.format(p)
73 else:
74 end_line = '#endif /* !defined({}) */'.format(p)
75 has_instance = False
76 for cls in classes:
77 for instance in cls.extract(s, st, end):
78 if has_instance is False:
79 has_instance = True
80 yield pair_start, start_line
Jerry Yud73d0a32022-03-29 16:37:51 +080081 yield instance.span()[0], instance
Jerry Yue78ee992021-09-22 15:42:14 +080082 if has_instance:
83 yield start, end_line
84
85 for match in pattern.finditer(source):
86
87 directive = match.groupdict()['directive'].strip()
88 param = match.groupdict()['param']
89 start, end = match.span()
90
91 if directive in ('if', 'ifndef', 'ifdef'):
92 stack.append((directive, param, start, end))
93 continue
94
95 if not stack:
96 raise CondDirectiveNotMatch()
97
98 pair_directive, pair_param, pair_start, pair_end = stack.pop()
99 yield from _yield_objects(source,
100 pair_directive,
101 pair_param,
102 pair_end,
103 start)
104
105 if directive == 'endif':
106 continue
107
108 if pair_directive == 'if':
109 directive = 'if'
110 param = "!( {} )".format(pair_param)
111 elif pair_directive == 'ifdef':
112 directive = 'ifndef'
113 param = pair_param
114 else:
115 directive = 'ifdef'
116 param = pair_param
117
118 stack.append((directive, param, start, end))
119 assert not stack, len(stack)
120
121
Jerry Yue78ee992021-09-22 15:42:14 +0800122class EnumDefinition:
123 """
124 Generate helper functions around enumeration.
125
Jerry Yu6389b252021-12-02 10:28:40 +0800126 Currently, it generate translation function from enum value to string.
Jerry Yue78ee992021-09-22 15:42:14 +0800127 Enum definition looks like:
128 [typedef] enum [prefix name] { [body] } [suffix name];
129
130 Known limitation:
131 - the '}' and ';' SHOULD NOT exist in different macro blocks. Like
132 ```
133 enum test {
134 ....
135 #if defined(A)
136 ....
137 };
138 #else
139 ....
140 };
141 #endif
142 ```
143 """
144
145 @classmethod
146 def extract(cls, source_code, start=0, end=-1):
147 enum_pattern = re.compile(r'enum\s*(?P<prefix_name>\w*)\s*' +
148 r'{\s*(?P<body>[^}]*)}' +
149 r'\s*(?P<suffix_name>\w*)\s*;',
Jerry Yue6369b02021-12-02 13:51:26 +0800150 re.MULTILINE | re.DOTALL)
Jerry Yue78ee992021-09-22 15:42:14 +0800151
152 for match in enum_pattern.finditer(source_code, start, end):
153 yield EnumDefinition(source_code,
154 span=match.span(),
155 group=match.groupdict())
156
157 def __init__(self, source_code, span=None, group=None):
158 assert isinstance(group, dict)
159 prefix_name = group.get('prefix_name', None)
160 suffix_name = group.get('suffix_name', None)
161 body = group.get('body', None)
162 assert prefix_name or suffix_name
163 assert body
164 assert span
165 # If suffix_name exists, it is a typedef
166 self._prototype = suffix_name if suffix_name else 'enum ' + prefix_name
167 self._name = suffix_name if suffix_name else prefix_name
168 self._body = body
169 self._source = source_code
170 self._span = span
171
172 def __repr__(self):
173 return 'Enum({},{})'.format(self._name, self._span)
174
175 def __str__(self):
176 return repr(self)
177
178 def span(self):
179 return self._span
180
David Horstmann3be12712021-12-16 10:56:26 +0000181 def generate_translation_function(self):
Jerry Yue78ee992021-09-22 15:42:14 +0800182 """
183 Generate function for translating value to string
184 """
185 translation_table = []
186
187 for line in self._body.splitlines():
188
189 if line.strip().startswith('#'):
190 # Preprocess directive, keep it in table
191 translation_table.append(line.strip())
192 continue
193
194 if not line.strip():
195 continue
196
197 for field in line.strip().split(','):
198 if not field.strip():
199 continue
200 member = field.strip().split()[0]
201 translation_table.append(
Gilles Peskine9d7b24f2023-06-23 21:11:46 +0200202 '{space}case {member}:\n{space} return "{member}";'
Gilles Peskinefd235bc2023-06-20 17:48:18 +0200203 .format(member=member, space=' '*8)
Jerry Yue78ee992021-09-22 15:42:14 +0800204 )
205
206 body = textwrap.dedent('''\
207 const char *{name}_str( {prototype} in )
208 {{
Gilles Peskinefd235bc2023-06-20 17:48:18 +0200209 switch (in) {{
Jerry Yue78ee992021-09-22 15:42:14 +0800210 {translation_table}
Gilles Peskinefd235bc2023-06-20 17:48:18 +0200211 default:
212 return "UNKNOWN_VALUE";
Jerry Yue78ee992021-09-22 15:42:14 +0800213 }}
Jerry Yue78ee992021-09-22 15:42:14 +0800214 }}
215 ''')
216 body = body.format(translation_table='\n'.join(translation_table),
217 name=self._name,
218 prototype=self._prototype)
Gilles Peskineccbc3182021-12-15 12:55:37 +0100219 return body
Jerry Yue78ee992021-09-22 15:42:14 +0800220
Jerry Yufe24d1c2022-04-11 21:04:47 +0800221
Jerry Yubfcfe742022-02-22 16:41:39 +0800222class SignatureAlgorithmDefinition:
223 """
224 Generate helper functions for signature algorithms.
225
226 It generates translation function from signature algorithm define to string.
227 Signature algorithm definition looks like:
228 #define MBEDTLS_TLS1_3_SIG_[ upper case signature algorithm ] [ value(hex) ]
229
230 Known limitation:
231 - the definitions SHOULD exist in same macro blocks.
232 """
233
234 @classmethod
235 def extract(cls, source_code, start=0, end=-1):
236 sig_alg_pattern = re.compile(r'#define\s+(?P<name>MBEDTLS_TLS1_3_SIG_\w+)\s+' +
237 r'(?P<value>0[xX][0-9a-fA-F]+)$',
238 re.MULTILINE | re.DOTALL)
239 matches = list(sig_alg_pattern.finditer(source_code, start, end))
240 if matches:
241 yield SignatureAlgorithmDefinition(source_code, definitions=matches)
242
243 def __init__(self, source_code, definitions=None):
244 if definitions is None:
245 definitions = []
246 assert isinstance(definitions, list) and definitions
247 self._definitions = definitions
248 self._source = source_code
249
250 def __repr__(self):
251 return 'SigAlgs({})'.format(self._definitions[0].span())
252
253 def span(self):
254 return self._definitions[0].span()
Jerry Yufe24d1c2022-04-11 21:04:47 +0800255
Jerry Yubfcfe742022-02-22 16:41:39 +0800256 def __str__(self):
257 """
258 Generate function for translating value to string
259 """
260 translation_table = []
261 for m in self._definitions:
262 name = m.groupdict()['name']
Paul Elliott4a496512022-07-08 19:59:09 +0100263 return_val = name[len('MBEDTLS_TLS1_3_SIG_'):].lower()
Jerry Yubfcfe742022-02-22 16:41:39 +0800264 translation_table.append(
Paul Elliott4a496512022-07-08 19:59:09 +0100265 ' case {}:\n return "{}";'.format(name, return_val))
Jerry Yubfcfe742022-02-22 16:41:39 +0800266
267 body = textwrap.dedent('''\
268 const char *mbedtls_ssl_sig_alg_to_str( uint16_t in )
269 {{
270 switch( in )
271 {{
272 {translation_table}
273 }};
274
Andrzej Kurek5c65c572022-04-13 14:28:52 -0400275 return "UNKNOWN";
Jerry Yubfcfe742022-02-22 16:41:39 +0800276 }}''')
277 body = body.format(translation_table='\n'.join(translation_table))
278 return body
Jerry Yue6369b02021-12-02 13:51:26 +0800279
Jerry Yufe24d1c2022-04-11 21:04:47 +0800280
281class NamedGroupDefinition:
282 """
283 Generate helper functions for named group
284
285 It generates translation function from named group define to string.
Jerry Yuab8bea22022-05-05 11:19:38 +0800286 Named group definition looks like:
Jerry Yufe24d1c2022-04-11 21:04:47 +0800287 #define MBEDTLS_SSL_IANA_TLS_GROUP_[ upper case named group ] [ value(hex) ]
288
289 Known limitation:
Jerry Yuab8bea22022-05-05 11:19:38 +0800290 - the definitions SHOULD exist in same macro blocks.
Jerry Yufe24d1c2022-04-11 21:04:47 +0800291 """
292
293 @classmethod
294 def extract(cls, source_code, start=0, end=-1):
Jerry Yuab8bea22022-05-05 11:19:38 +0800295 named_group_pattern = re.compile(r'#define\s+(?P<name>MBEDTLS_SSL_IANA_TLS_GROUP_\w+)\s+' +
296 r'(?P<value>0[xX][0-9a-fA-F]+)$',
297 re.MULTILINE | re.DOTALL)
298 matches = list(named_group_pattern.finditer(source_code, start, end))
Jerry Yufe24d1c2022-04-11 21:04:47 +0800299 if matches:
300 yield NamedGroupDefinition(source_code, definitions=matches)
301
302 def __init__(self, source_code, definitions=None):
303 if definitions is None:
304 definitions = []
305 assert isinstance(definitions, list) and definitions
306 self._definitions = definitions
307 self._source = source_code
308
309 def __repr__(self):
310 return 'NamedGroup({})'.format(self._definitions[0].span())
311
312 def span(self):
313 return self._definitions[0].span()
314
315 def __str__(self):
316 """
317 Generate function for translating value to string
318 """
319 translation_table = []
320 for m in self._definitions:
321 name = m.groupdict()['name']
322 iana_name = name[len('MBEDTLS_SSL_IANA_TLS_GROUP_'):].lower()
Paul Elliottfe9d43c2022-07-08 17:27:25 +0100323 translation_table.append(' case {}:\n return "{}";'.format(name, iana_name))
Jerry Yufe24d1c2022-04-11 21:04:47 +0800324
325 body = textwrap.dedent('''\
326 const char *mbedtls_ssl_named_group_to_str( uint16_t in )
327 {{
328 switch( in )
329 {{
330 {translation_table}
331 }};
332
Turiiya20f44882024-05-18 18:04:58 +0200333 return "UNKNOWN";
Jerry Yufe24d1c2022-04-11 21:04:47 +0800334 }}''')
335 body = body.format(translation_table='\n'.join(translation_table))
336 return body
337
338
Jerry Yue78ee992021-09-22 15:42:14 +0800339OUTPUT_C_TEMPLATE = '''\
340/* Automatically generated by generate_ssl_debug_helpers.py. DO NOT EDIT. */
341
Gilles Peskine863b96a2021-12-16 10:04:58 +0100342/**
Dave Rodgmanb8f76942022-04-22 15:20:46 +0100343 * \\file ssl_debug_helpers_generated.c
Gilles Peskine863b96a2021-12-16 10:04:58 +0100344 *
Dave Rodgmanb8f76942022-04-22 15:20:46 +0100345 * \\brief Automatically generated helper functions for debugging
Gilles Peskine863b96a2021-12-16 10:04:58 +0100346 */
347/*
348 * Copyright The Mbed TLS Contributors
Dave Rodgman16799db2023-11-02 19:47:20 +0000349 * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
Gilles Peskine863b96a2021-12-16 10:04:58 +0100350 *
Gilles Peskine863b96a2021-12-16 10:04:58 +0100351 */
352
Jerry Yue78ee992021-09-22 15:42:14 +0800353#include "common.h"
354
355#if defined(MBEDTLS_DEBUG_C)
356
Gilles Peskine923d5c92021-12-15 12:56:54 +0100357#include "ssl_debug_helpers.h"
Jerry Yue78ee992021-09-22 15:42:14 +0800358
359{functions}
360
361#endif /* MBEDTLS_DEBUG_C */
362/* End of automatically generated file. */
363
364'''
365
Jerry Yue78ee992021-09-22 15:42:14 +0800366
Jerry Yue6369b02021-12-02 13:51:26 +0800367def generate_ssl_debug_helpers(output_directory, mbedtls_root):
Jerry Yue78ee992021-09-22 15:42:14 +0800368 """
369 Generate functions of debug helps
370 """
Jerry Yufe24d1c2022-04-11 21:04:47 +0800371 mbedtls_root = os.path.abspath(
372 mbedtls_root or build_tree.guess_mbedtls_root())
Jerry Yue6369b02021-12-02 13:51:26 +0800373 with open(os.path.join(mbedtls_root, 'include/mbedtls/ssl.h')) as f:
Jerry Yue78ee992021-09-22 15:42:14 +0800374 source_code = remove_c_comments(f.read())
375
376 definitions = dict()
Jerry Yubfcfe742022-02-22 16:41:39 +0800377 for start, instance in preprocess_c_source_code(source_code,
378 EnumDefinition,
Jerry Yufe24d1c2022-04-11 21:04:47 +0800379 SignatureAlgorithmDefinition,
380 NamedGroupDefinition):
Jerry Yue78ee992021-09-22 15:42:14 +0800381 if start in definitions:
382 continue
383 if isinstance(instance, EnumDefinition):
David Horstmann3be12712021-12-16 10:56:26 +0000384 definition = instance.generate_translation_function()
Jerry Yue78ee992021-09-22 15:42:14 +0800385 else:
386 definition = instance
Jerry Yue78ee992021-09-22 15:42:14 +0800387 definitions[start] = definition
Jerry Yue78ee992021-09-22 15:42:14 +0800388
Jerry Yue6369b02021-12-02 13:51:26 +0800389 function_definitions = [str(v) for _, v in sorted(definitions.items())]
Jerry Yue6369b02021-12-02 13:51:26 +0800390 if output_directory == sys.stdout:
Jerry Yue6369b02021-12-02 13:51:26 +0800391 sys.stdout.write(OUTPUT_C_TEMPLATE.format(
392 functions='\n'.join(function_definitions)))
393 else:
394 with open(os.path.join(output_directory, 'ssl_debug_helpers_generated.c'), 'w') as f:
395 f.write(OUTPUT_C_TEMPLATE.format(
396 functions='\n'.join(function_definitions)))
Jerry Yue78ee992021-09-22 15:42:14 +0800397
Jerry Yue78ee992021-09-22 15:42:14 +0800398
Jerry Yue6369b02021-12-02 13:51:26 +0800399def main():
400 """
401 Command line entry
402 """
403 parser = argparse.ArgumentParser()
Jerry Yu0cb2cf62021-12-10 14:21:27 +0800404 parser.add_argument('--mbedtls-root', nargs='?', default=None,
Jerry Yue6369b02021-12-02 13:51:26 +0800405 help='root directory of mbedtls source code')
406 parser.add_argument('output_directory', nargs='?',
Jerry Yu9817e3e2021-12-02 14:32:58 +0800407 default='library', help='source/header files location')
Jerry Yue78ee992021-09-22 15:42:14 +0800408
Jerry Yue6369b02021-12-02 13:51:26 +0800409 args = parser.parse_args()
410
411 generate_ssl_debug_helpers(args.output_directory, args.mbedtls_root)
412 return 0
Jerry Yue78ee992021-09-22 15:42:14 +0800413
414
415if __name__ == '__main__':
Jerry Yue6369b02021-12-02 13:51:26 +0800416 sys.exit(main())