blob: 043cef18808c6f38ae71dad4576ce33b7f60c07b [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
Jerry Yue78ee992021-09-22 15:42:14 +080017from mbedtls_dev import build_tree
18
Jerry Yue6369b02021-12-02 13:51:26 +080019
Jerry Yue78ee992021-09-22 15:42:14 +080020def remove_c_comments(string):
21 """
22 Remove C style comments from input string
23 """
24 string_pattern = r"(?P<string>\".*?\"|\'.*?\')"
25 comment_pattern = r"(?P<comment>/\*.*?\*/|//[^\r\n]*$)"
26 pattern = re.compile(string_pattern + r'|' + comment_pattern,
Jerry Yue6369b02021-12-02 13:51:26 +080027 re.MULTILINE | re.DOTALL)
28
Jerry Yue78ee992021-09-22 15:42:14 +080029 def replacer(match):
30 if match.lastgroup == 'comment':
31 return ""
32 return match.group()
33 return pattern.sub(replacer, string)
34
Jerry Yue6369b02021-12-02 13:51:26 +080035
Jerry Yue78ee992021-09-22 15:42:14 +080036class CondDirectiveNotMatch(Exception):
37 pass
38
Jerry Yue6369b02021-12-02 13:51:26 +080039
Jerry Yue988f0f2021-11-11 13:22:20 +080040def preprocess_c_source_code(source, *classes):
Jerry Yue78ee992021-09-22 15:42:14 +080041 """
42 Simple preprocessor for C source code.
43
Shaun Case8b0ecbc2021-12-20 21:14:10 -080044 Only processes condition directives without expanding them.
Jerry Yu6389b252021-12-02 10:28:40 +080045 Yield object according to the classes input. Most match firstly
Jerry Yue78ee992021-09-22 15:42:14 +080046
Jerry Yu6389b252021-12-02 10:28:40 +080047 If the directive pair does not match , raise CondDirectiveNotMatch.
Jerry Yue78ee992021-09-22 15:42:14 +080048
49 Assume source code does not include comments and compile pass.
50
51 """
52
53 pattern = re.compile(r"^[ \t]*#[ \t]*" +
54 r"(?P<directive>(if[ \t]|ifndef[ \t]|ifdef[ \t]|else|endif))" +
55 r"[ \t]*(?P<param>(.*\\\n)*.*$)",
56 re.MULTILINE)
57 stack = []
58
59 def _yield_objects(s, d, p, st, end):
60 """
61 Output matched source piece
62 """
63 nonlocal stack
64 start_line, end_line = '', ''
65 if stack:
66 start_line = '#{} {}'.format(d, p)
67 if d == 'if':
68 end_line = '#endif /* {} */'.format(p)
69 elif d == 'ifdef':
70 end_line = '#endif /* defined({}) */'.format(p)
71 else:
72 end_line = '#endif /* !defined({}) */'.format(p)
73 has_instance = False
74 for cls in classes:
75 for instance in cls.extract(s, st, end):
76 if has_instance is False:
77 has_instance = True
78 yield pair_start, start_line
Jerry Yud73d0a32022-03-29 16:37:51 +080079 yield instance.span()[0], instance
Jerry Yue78ee992021-09-22 15:42:14 +080080 if has_instance:
81 yield start, end_line
82
83 for match in pattern.finditer(source):
84
85 directive = match.groupdict()['directive'].strip()
86 param = match.groupdict()['param']
87 start, end = match.span()
88
89 if directive in ('if', 'ifndef', 'ifdef'):
90 stack.append((directive, param, start, end))
91 continue
92
93 if not stack:
94 raise CondDirectiveNotMatch()
95
96 pair_directive, pair_param, pair_start, pair_end = stack.pop()
97 yield from _yield_objects(source,
98 pair_directive,
99 pair_param,
100 pair_end,
101 start)
102
103 if directive == 'endif':
104 continue
105
106 if pair_directive == 'if':
107 directive = 'if'
108 param = "!( {} )".format(pair_param)
109 elif pair_directive == 'ifdef':
110 directive = 'ifndef'
111 param = pair_param
112 else:
113 directive = 'ifdef'
114 param = pair_param
115
116 stack.append((directive, param, start, end))
117 assert not stack, len(stack)
118
119
Jerry Yue78ee992021-09-22 15:42:14 +0800120class EnumDefinition:
121 """
122 Generate helper functions around enumeration.
123
Jerry Yu6389b252021-12-02 10:28:40 +0800124 Currently, it generate translation function from enum value to string.
Jerry Yue78ee992021-09-22 15:42:14 +0800125 Enum definition looks like:
126 [typedef] enum [prefix name] { [body] } [suffix name];
127
128 Known limitation:
129 - the '}' and ';' SHOULD NOT exist in different macro blocks. Like
130 ```
131 enum test {
132 ....
133 #if defined(A)
134 ....
135 };
136 #else
137 ....
138 };
139 #endif
140 ```
141 """
142
143 @classmethod
144 def extract(cls, source_code, start=0, end=-1):
145 enum_pattern = re.compile(r'enum\s*(?P<prefix_name>\w*)\s*' +
146 r'{\s*(?P<body>[^}]*)}' +
147 r'\s*(?P<suffix_name>\w*)\s*;',
Jerry Yue6369b02021-12-02 13:51:26 +0800148 re.MULTILINE | re.DOTALL)
Jerry Yue78ee992021-09-22 15:42:14 +0800149
150 for match in enum_pattern.finditer(source_code, start, end):
151 yield EnumDefinition(source_code,
152 span=match.span(),
153 group=match.groupdict())
154
155 def __init__(self, source_code, span=None, group=None):
156 assert isinstance(group, dict)
157 prefix_name = group.get('prefix_name', None)
158 suffix_name = group.get('suffix_name', None)
159 body = group.get('body', None)
160 assert prefix_name or suffix_name
161 assert body
162 assert span
163 # If suffix_name exists, it is a typedef
164 self._prototype = suffix_name if suffix_name else 'enum ' + prefix_name
165 self._name = suffix_name if suffix_name else prefix_name
166 self._body = body
167 self._source = source_code
168 self._span = span
169
170 def __repr__(self):
171 return 'Enum({},{})'.format(self._name, self._span)
172
173 def __str__(self):
174 return repr(self)
175
176 def span(self):
177 return self._span
178
David Horstmann3be12712021-12-16 10:56:26 +0000179 def generate_translation_function(self):
Jerry Yue78ee992021-09-22 15:42:14 +0800180 """
181 Generate function for translating value to string
182 """
183 translation_table = []
184
185 for line in self._body.splitlines():
186
187 if line.strip().startswith('#'):
188 # Preprocess directive, keep it in table
189 translation_table.append(line.strip())
190 continue
191
192 if not line.strip():
193 continue
194
195 for field in line.strip().split(','):
196 if not field.strip():
197 continue
198 member = field.strip().split()[0]
199 translation_table.append(
Gilles Peskine9d7b24f2023-06-23 21:11:46 +0200200 '{space}case {member}:\n{space} return "{member}";'
Gilles Peskinefd235bc2023-06-20 17:48:18 +0200201 .format(member=member, space=' '*8)
Jerry Yue78ee992021-09-22 15:42:14 +0800202 )
203
204 body = textwrap.dedent('''\
205 const char *{name}_str( {prototype} in )
206 {{
Gilles Peskinefd235bc2023-06-20 17:48:18 +0200207 switch (in) {{
Jerry Yue78ee992021-09-22 15:42:14 +0800208 {translation_table}
Gilles Peskinefd235bc2023-06-20 17:48:18 +0200209 default:
210 return "UNKNOWN_VALUE";
Jerry Yue78ee992021-09-22 15:42:14 +0800211 }}
Jerry Yue78ee992021-09-22 15:42:14 +0800212 }}
213 ''')
214 body = body.format(translation_table='\n'.join(translation_table),
215 name=self._name,
216 prototype=self._prototype)
Gilles Peskineccbc3182021-12-15 12:55:37 +0100217 return body
Jerry Yue78ee992021-09-22 15:42:14 +0800218
Jerry Yufe24d1c2022-04-11 21:04:47 +0800219
Jerry Yubfcfe742022-02-22 16:41:39 +0800220class SignatureAlgorithmDefinition:
221 """
222 Generate helper functions for signature algorithms.
223
224 It generates translation function from signature algorithm define to string.
225 Signature algorithm definition looks like:
226 #define MBEDTLS_TLS1_3_SIG_[ upper case signature algorithm ] [ value(hex) ]
227
228 Known limitation:
229 - the definitions SHOULD exist in same macro blocks.
230 """
231
232 @classmethod
233 def extract(cls, source_code, start=0, end=-1):
234 sig_alg_pattern = re.compile(r'#define\s+(?P<name>MBEDTLS_TLS1_3_SIG_\w+)\s+' +
235 r'(?P<value>0[xX][0-9a-fA-F]+)$',
236 re.MULTILINE | re.DOTALL)
237 matches = list(sig_alg_pattern.finditer(source_code, start, end))
238 if matches:
239 yield SignatureAlgorithmDefinition(source_code, definitions=matches)
240
241 def __init__(self, source_code, definitions=None):
242 if definitions is None:
243 definitions = []
244 assert isinstance(definitions, list) and definitions
245 self._definitions = definitions
246 self._source = source_code
247
248 def __repr__(self):
249 return 'SigAlgs({})'.format(self._definitions[0].span())
250
251 def span(self):
252 return self._definitions[0].span()
Jerry Yufe24d1c2022-04-11 21:04:47 +0800253
Jerry Yubfcfe742022-02-22 16:41:39 +0800254 def __str__(self):
255 """
256 Generate function for translating value to string
257 """
258 translation_table = []
259 for m in self._definitions:
260 name = m.groupdict()['name']
Paul Elliott4a496512022-07-08 19:59:09 +0100261 return_val = name[len('MBEDTLS_TLS1_3_SIG_'):].lower()
Jerry Yubfcfe742022-02-22 16:41:39 +0800262 translation_table.append(
Paul Elliott4a496512022-07-08 19:59:09 +0100263 ' case {}:\n return "{}";'.format(name, return_val))
Jerry Yubfcfe742022-02-22 16:41:39 +0800264
265 body = textwrap.dedent('''\
266 const char *mbedtls_ssl_sig_alg_to_str( uint16_t in )
267 {{
268 switch( in )
269 {{
270 {translation_table}
271 }};
272
Andrzej Kurek5c65c572022-04-13 14:28:52 -0400273 return "UNKNOWN";
Jerry Yubfcfe742022-02-22 16:41:39 +0800274 }}''')
275 body = body.format(translation_table='\n'.join(translation_table))
276 return body
Jerry Yue6369b02021-12-02 13:51:26 +0800277
Jerry Yufe24d1c2022-04-11 21:04:47 +0800278
279class NamedGroupDefinition:
280 """
281 Generate helper functions for named group
282
283 It generates translation function from named group define to string.
Jerry Yuab8bea22022-05-05 11:19:38 +0800284 Named group definition looks like:
Jerry Yufe24d1c2022-04-11 21:04:47 +0800285 #define MBEDTLS_SSL_IANA_TLS_GROUP_[ upper case named group ] [ value(hex) ]
286
287 Known limitation:
Jerry Yuab8bea22022-05-05 11:19:38 +0800288 - the definitions SHOULD exist in same macro blocks.
Jerry Yufe24d1c2022-04-11 21:04:47 +0800289 """
290
291 @classmethod
292 def extract(cls, source_code, start=0, end=-1):
Jerry Yuab8bea22022-05-05 11:19:38 +0800293 named_group_pattern = re.compile(r'#define\s+(?P<name>MBEDTLS_SSL_IANA_TLS_GROUP_\w+)\s+' +
294 r'(?P<value>0[xX][0-9a-fA-F]+)$',
295 re.MULTILINE | re.DOTALL)
296 matches = list(named_group_pattern.finditer(source_code, start, end))
Jerry Yufe24d1c2022-04-11 21:04:47 +0800297 if matches:
298 yield NamedGroupDefinition(source_code, definitions=matches)
299
300 def __init__(self, source_code, definitions=None):
301 if definitions is None:
302 definitions = []
303 assert isinstance(definitions, list) and definitions
304 self._definitions = definitions
305 self._source = source_code
306
307 def __repr__(self):
308 return 'NamedGroup({})'.format(self._definitions[0].span())
309
310 def span(self):
311 return self._definitions[0].span()
312
313 def __str__(self):
314 """
315 Generate function for translating value to string
316 """
317 translation_table = []
318 for m in self._definitions:
319 name = m.groupdict()['name']
320 iana_name = name[len('MBEDTLS_SSL_IANA_TLS_GROUP_'):].lower()
Paul Elliottfe9d43c2022-07-08 17:27:25 +0100321 translation_table.append(' case {}:\n return "{}";'.format(name, iana_name))
Jerry Yufe24d1c2022-04-11 21:04:47 +0800322
323 body = textwrap.dedent('''\
324 const char *mbedtls_ssl_named_group_to_str( uint16_t in )
325 {{
326 switch( in )
327 {{
328 {translation_table}
329 }};
330
Turiiya20f44882024-05-18 18:04:58 +0200331 return "UNKNOWN";
Jerry Yufe24d1c2022-04-11 21:04:47 +0800332 }}''')
333 body = body.format(translation_table='\n'.join(translation_table))
334 return body
335
336
Jerry Yue78ee992021-09-22 15:42:14 +0800337OUTPUT_C_TEMPLATE = '''\
338/* Automatically generated by generate_ssl_debug_helpers.py. DO NOT EDIT. */
339
Gilles Peskine863b96a2021-12-16 10:04:58 +0100340/**
Dave Rodgmanb8f76942022-04-22 15:20:46 +0100341 * \\file ssl_debug_helpers_generated.c
Gilles Peskine863b96a2021-12-16 10:04:58 +0100342 *
Dave Rodgmanb8f76942022-04-22 15:20:46 +0100343 * \\brief Automatically generated helper functions for debugging
Gilles Peskine863b96a2021-12-16 10:04:58 +0100344 */
345/*
346 * Copyright The Mbed TLS Contributors
Dave Rodgman16799db2023-11-02 19:47:20 +0000347 * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
Gilles Peskine863b96a2021-12-16 10:04:58 +0100348 *
Gilles Peskine863b96a2021-12-16 10:04:58 +0100349 */
350
Jerry Yue78ee992021-09-22 15:42:14 +0800351#include "common.h"
352
353#if defined(MBEDTLS_DEBUG_C)
354
Gilles Peskine923d5c92021-12-15 12:56:54 +0100355#include "ssl_debug_helpers.h"
Jerry Yue78ee992021-09-22 15:42:14 +0800356
357{functions}
358
359#endif /* MBEDTLS_DEBUG_C */
360/* End of automatically generated file. */
361
362'''
363
Jerry Yue78ee992021-09-22 15:42:14 +0800364
Jerry Yue6369b02021-12-02 13:51:26 +0800365def generate_ssl_debug_helpers(output_directory, mbedtls_root):
Jerry Yue78ee992021-09-22 15:42:14 +0800366 """
367 Generate functions of debug helps
368 """
Jerry Yufe24d1c2022-04-11 21:04:47 +0800369 mbedtls_root = os.path.abspath(
370 mbedtls_root or build_tree.guess_mbedtls_root())
Jerry Yue6369b02021-12-02 13:51:26 +0800371 with open(os.path.join(mbedtls_root, 'include/mbedtls/ssl.h')) as f:
Jerry Yue78ee992021-09-22 15:42:14 +0800372 source_code = remove_c_comments(f.read())
373
374 definitions = dict()
Jerry Yubfcfe742022-02-22 16:41:39 +0800375 for start, instance in preprocess_c_source_code(source_code,
376 EnumDefinition,
Jerry Yufe24d1c2022-04-11 21:04:47 +0800377 SignatureAlgorithmDefinition,
378 NamedGroupDefinition):
Jerry Yue78ee992021-09-22 15:42:14 +0800379 if start in definitions:
380 continue
381 if isinstance(instance, EnumDefinition):
David Horstmann3be12712021-12-16 10:56:26 +0000382 definition = instance.generate_translation_function()
Jerry Yue78ee992021-09-22 15:42:14 +0800383 else:
384 definition = instance
Jerry Yue78ee992021-09-22 15:42:14 +0800385 definitions[start] = definition
Jerry Yue78ee992021-09-22 15:42:14 +0800386
Jerry Yue6369b02021-12-02 13:51:26 +0800387 function_definitions = [str(v) for _, v in sorted(definitions.items())]
Jerry Yue6369b02021-12-02 13:51:26 +0800388 if output_directory == sys.stdout:
Jerry Yue6369b02021-12-02 13:51:26 +0800389 sys.stdout.write(OUTPUT_C_TEMPLATE.format(
390 functions='\n'.join(function_definitions)))
391 else:
392 with open(os.path.join(output_directory, 'ssl_debug_helpers_generated.c'), 'w') as f:
393 f.write(OUTPUT_C_TEMPLATE.format(
394 functions='\n'.join(function_definitions)))
Jerry Yue78ee992021-09-22 15:42:14 +0800395
Jerry Yue78ee992021-09-22 15:42:14 +0800396
Jerry Yue6369b02021-12-02 13:51:26 +0800397def main():
398 """
399 Command line entry
400 """
401 parser = argparse.ArgumentParser()
Jerry Yu0cb2cf62021-12-10 14:21:27 +0800402 parser.add_argument('--mbedtls-root', nargs='?', default=None,
Jerry Yue6369b02021-12-02 13:51:26 +0800403 help='root directory of mbedtls source code')
404 parser.add_argument('output_directory', nargs='?',
Jerry Yu9817e3e2021-12-02 14:32:58 +0800405 default='library', help='source/header files location')
Jerry Yue78ee992021-09-22 15:42:14 +0800406
Jerry Yue6369b02021-12-02 13:51:26 +0800407 args = parser.parse_args()
408
409 generate_ssl_debug_helpers(args.output_directory, args.mbedtls_root)
410 return 0
Jerry Yue78ee992021-09-22 15:42:14 +0800411
412
413if __name__ == '__main__':
Jerry Yue6369b02021-12-02 13:51:26 +0800414 sys.exit(main())