blob: a0544f1537dff6ccedc86de725a8e3083ba69e1a [file] [log] [blame]
Tom Rini0344c602024-10-08 13:56:50 -06001#!/usr/bin/env python3
2
3"""Generate library/ssl_debug_helpers_generated.c
4
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
11# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
12import sys
13import re
14import os
15import textwrap
16import argparse
17from mbedtls_dev import build_tree
18
19
20def 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,
27 re.MULTILINE | re.DOTALL)
28
29 def replacer(match):
30 if match.lastgroup == 'comment':
31 return ""
32 return match.group()
33 return pattern.sub(replacer, string)
34
35
36class CondDirectiveNotMatch(Exception):
37 pass
38
39
40def preprocess_c_source_code(source, *classes):
41 """
42 Simple preprocessor for C source code.
43
44 Only processes condition directives without expanding them.
45 Yield object according to the classes input. Most match firstly
46
47 If the directive pair does not match , raise CondDirectiveNotMatch.
48
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
79 yield instance.span()[0], instance
80 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
120class EnumDefinition:
121 """
122 Generate helper functions around enumeration.
123
124 Currently, it generate translation function from enum value to string.
125 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*;',
148 re.MULTILINE | re.DOTALL)
149
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
179 def generate_translation_function(self):
180 """
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(
200 '{space}case {member}:\n{space} return "{member}";'
201 .format(member=member, space=' '*8)
202 )
203
204 body = textwrap.dedent('''\
205 const char *{name}_str( {prototype} in )
206 {{
207 switch (in) {{
208 {translation_table}
209 default:
210 return "UNKNOWN_VALUE";
211 }}
212 }}
213 ''')
214 body = body.format(translation_table='\n'.join(translation_table),
215 name=self._name,
216 prototype=self._prototype)
217 return body
218
219
220class 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()
253
254 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']
261 return_val = name[len('MBEDTLS_TLS1_3_SIG_'):].lower()
262 translation_table.append(
263 ' case {}:\n return "{}";'.format(name, return_val))
264
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
273 return "UNKNOWN";
274 }}''')
275 body = body.format(translation_table='\n'.join(translation_table))
276 return body
277
278
279class NamedGroupDefinition:
280 """
281 Generate helper functions for named group
282
283 It generates translation function from named group define to string.
284 Named group definition looks like:
285 #define MBEDTLS_SSL_IANA_TLS_GROUP_[ upper case named group ] [ value(hex) ]
286
287 Known limitation:
288 - the definitions SHOULD exist in same macro blocks.
289 """
290
291 @classmethod
292 def extract(cls, source_code, start=0, end=-1):
293 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))
297 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()
321 translation_table.append(' case {}:\n return "{}";'.format(name, iana_name))
322
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
331 return "UNKOWN";
332 }}''')
333 body = body.format(translation_table='\n'.join(translation_table))
334 return body
335
336
337OUTPUT_C_TEMPLATE = '''\
338/* Automatically generated by generate_ssl_debug_helpers.py. DO NOT EDIT. */
339
340/**
341 * \\file ssl_debug_helpers_generated.c
342 *
343 * \\brief Automatically generated helper functions for debugging
344 */
345/*
346 * Copyright The Mbed TLS Contributors
347 * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
348 *
349 */
350
351#include "common.h"
352
353#if defined(MBEDTLS_DEBUG_C)
354
355#include "ssl_debug_helpers.h"
356
357{functions}
358
359#endif /* MBEDTLS_DEBUG_C */
360/* End of automatically generated file. */
361
362'''
363
364
365def generate_ssl_debug_helpers(output_directory, mbedtls_root):
366 """
367 Generate functions of debug helps
368 """
369 mbedtls_root = os.path.abspath(
370 mbedtls_root or build_tree.guess_mbedtls_root())
371 with open(os.path.join(mbedtls_root, 'include/mbedtls/ssl.h')) as f:
372 source_code = remove_c_comments(f.read())
373
374 definitions = dict()
375 for start, instance in preprocess_c_source_code(source_code,
376 EnumDefinition,
377 SignatureAlgorithmDefinition,
378 NamedGroupDefinition):
379 if start in definitions:
380 continue
381 if isinstance(instance, EnumDefinition):
382 definition = instance.generate_translation_function()
383 else:
384 definition = instance
385 definitions[start] = definition
386
387 function_definitions = [str(v) for _, v in sorted(definitions.items())]
388 if output_directory == sys.stdout:
389 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)))
395
396
397def main():
398 """
399 Command line entry
400 """
401 parser = argparse.ArgumentParser()
402 parser.add_argument('--mbedtls-root', nargs='?', default=None,
403 help='root directory of mbedtls source code')
404 parser.add_argument('output_directory', nargs='?',
405 default='library', help='source/header files location')
406
407 args = parser.parse_args()
408
409 generate_ssl_debug_helpers(args.output_directory, args.mbedtls_root)
410 return 0
411
412
413if __name__ == '__main__':
414 sys.exit(main())