blob: 0af7b8f39eca0e9445c410d1651a654209c4b7a5 [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
11# SPDX-License-Identifier: Apache-2.0
12#
13# Licensed under the Apache License, Version 2.0 (the "License"); you may
14# not use this file except in compliance with the License.
15# You may obtain a copy of the License at
16#
17# http://www.apache.org/licenses/LICENSE-2.0
18#
19# Unless required by applicable law or agreed to in writing, software
20# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
21# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22# See the License for the specific language governing permissions and
23# limitations under the License.
24import sys
25import re
26import os
27import textwrap
Jerry Yue6369b02021-12-02 13:51:26 +080028import argparse
Jerry Yue78ee992021-09-22 15:42:14 +080029from mbedtls_dev import build_tree
30
Jerry Yue6369b02021-12-02 13:51:26 +080031
Jerry Yue78ee992021-09-22 15:42:14 +080032def remove_c_comments(string):
33 """
34 Remove C style comments from input string
35 """
36 string_pattern = r"(?P<string>\".*?\"|\'.*?\')"
37 comment_pattern = r"(?P<comment>/\*.*?\*/|//[^\r\n]*$)"
38 pattern = re.compile(string_pattern + r'|' + comment_pattern,
Jerry Yue6369b02021-12-02 13:51:26 +080039 re.MULTILINE | re.DOTALL)
40
Jerry Yue78ee992021-09-22 15:42:14 +080041 def replacer(match):
42 if match.lastgroup == 'comment':
43 return ""
44 return match.group()
45 return pattern.sub(replacer, string)
46
Jerry Yue6369b02021-12-02 13:51:26 +080047
Jerry Yue78ee992021-09-22 15:42:14 +080048class CondDirectiveNotMatch(Exception):
49 pass
50
Jerry Yue6369b02021-12-02 13:51:26 +080051
Jerry Yue988f0f2021-11-11 13:22:20 +080052def preprocess_c_source_code(source, *classes):
Jerry Yue78ee992021-09-22 15:42:14 +080053 """
54 Simple preprocessor for C source code.
55
Shaun Case8b0ecbc2021-12-20 21:14:10 -080056 Only processes condition directives without expanding them.
Jerry Yu6389b252021-12-02 10:28:40 +080057 Yield object according to the classes input. Most match firstly
Jerry Yue78ee992021-09-22 15:42:14 +080058
Jerry Yu6389b252021-12-02 10:28:40 +080059 If the directive pair does not match , raise CondDirectiveNotMatch.
Jerry Yue78ee992021-09-22 15:42:14 +080060
61 Assume source code does not include comments and compile pass.
62
63 """
64
65 pattern = re.compile(r"^[ \t]*#[ \t]*" +
66 r"(?P<directive>(if[ \t]|ifndef[ \t]|ifdef[ \t]|else|endif))" +
67 r"[ \t]*(?P<param>(.*\\\n)*.*$)",
68 re.MULTILINE)
69 stack = []
70
71 def _yield_objects(s, d, p, st, end):
72 """
73 Output matched source piece
74 """
75 nonlocal stack
76 start_line, end_line = '', ''
77 if stack:
78 start_line = '#{} {}'.format(d, p)
79 if d == 'if':
80 end_line = '#endif /* {} */'.format(p)
81 elif d == 'ifdef':
82 end_line = '#endif /* defined({}) */'.format(p)
83 else:
84 end_line = '#endif /* !defined({}) */'.format(p)
85 has_instance = False
86 for cls in classes:
87 for instance in cls.extract(s, st, end):
88 if has_instance is False:
89 has_instance = True
90 yield pair_start, start_line
Jerry Yud73d0a32022-03-29 16:37:51 +080091 yield instance.span()[0], instance
Jerry Yue78ee992021-09-22 15:42:14 +080092 if has_instance:
93 yield start, end_line
94
95 for match in pattern.finditer(source):
96
97 directive = match.groupdict()['directive'].strip()
98 param = match.groupdict()['param']
99 start, end = match.span()
100
101 if directive in ('if', 'ifndef', 'ifdef'):
102 stack.append((directive, param, start, end))
103 continue
104
105 if not stack:
106 raise CondDirectiveNotMatch()
107
108 pair_directive, pair_param, pair_start, pair_end = stack.pop()
109 yield from _yield_objects(source,
110 pair_directive,
111 pair_param,
112 pair_end,
113 start)
114
115 if directive == 'endif':
116 continue
117
118 if pair_directive == 'if':
119 directive = 'if'
120 param = "!( {} )".format(pair_param)
121 elif pair_directive == 'ifdef':
122 directive = 'ifndef'
123 param = pair_param
124 else:
125 directive = 'ifdef'
126 param = pair_param
127
128 stack.append((directive, param, start, end))
129 assert not stack, len(stack)
130
131
Jerry Yue78ee992021-09-22 15:42:14 +0800132class EnumDefinition:
133 """
134 Generate helper functions around enumeration.
135
Jerry Yu6389b252021-12-02 10:28:40 +0800136 Currently, it generate translation function from enum value to string.
Jerry Yue78ee992021-09-22 15:42:14 +0800137 Enum definition looks like:
138 [typedef] enum [prefix name] { [body] } [suffix name];
139
140 Known limitation:
141 - the '}' and ';' SHOULD NOT exist in different macro blocks. Like
142 ```
143 enum test {
144 ....
145 #if defined(A)
146 ....
147 };
148 #else
149 ....
150 };
151 #endif
152 ```
153 """
154
155 @classmethod
156 def extract(cls, source_code, start=0, end=-1):
157 enum_pattern = re.compile(r'enum\s*(?P<prefix_name>\w*)\s*' +
158 r'{\s*(?P<body>[^}]*)}' +
159 r'\s*(?P<suffix_name>\w*)\s*;',
Jerry Yue6369b02021-12-02 13:51:26 +0800160 re.MULTILINE | re.DOTALL)
Jerry Yue78ee992021-09-22 15:42:14 +0800161
162 for match in enum_pattern.finditer(source_code, start, end):
163 yield EnumDefinition(source_code,
164 span=match.span(),
165 group=match.groupdict())
166
167 def __init__(self, source_code, span=None, group=None):
168 assert isinstance(group, dict)
169 prefix_name = group.get('prefix_name', None)
170 suffix_name = group.get('suffix_name', None)
171 body = group.get('body', None)
172 assert prefix_name or suffix_name
173 assert body
174 assert span
175 # If suffix_name exists, it is a typedef
176 self._prototype = suffix_name if suffix_name else 'enum ' + prefix_name
177 self._name = suffix_name if suffix_name else prefix_name
178 self._body = body
179 self._source = source_code
180 self._span = span
181
182 def __repr__(self):
183 return 'Enum({},{})'.format(self._name, self._span)
184
185 def __str__(self):
186 return repr(self)
187
188 def span(self):
189 return self._span
190
David Horstmann3be12712021-12-16 10:56:26 +0000191 def generate_translation_function(self):
Jerry Yue78ee992021-09-22 15:42:14 +0800192 """
193 Generate function for translating value to string
194 """
195 translation_table = []
196
197 for line in self._body.splitlines():
198
199 if line.strip().startswith('#'):
200 # Preprocess directive, keep it in table
201 translation_table.append(line.strip())
202 continue
203
204 if not line.strip():
205 continue
206
207 for field in line.strip().split(','):
208 if not field.strip():
209 continue
210 member = field.strip().split()[0]
211 translation_table.append(
Gilles Peskinefd235bc2023-06-20 17:48:18 +0200212 '{space}case {member}:\n{space} return "{member};";'
213 .format(member=member, space=' '*8)
Jerry Yue78ee992021-09-22 15:42:14 +0800214 )
215
216 body = textwrap.dedent('''\
217 const char *{name}_str( {prototype} in )
218 {{
Gilles Peskinefd235bc2023-06-20 17:48:18 +0200219 switch (in) {{
Jerry Yue78ee992021-09-22 15:42:14 +0800220 {translation_table}
Gilles Peskinefd235bc2023-06-20 17:48:18 +0200221 default:
222 return "UNKNOWN_VALUE";
Jerry Yue78ee992021-09-22 15:42:14 +0800223 }}
Jerry Yue78ee992021-09-22 15:42:14 +0800224 }}
225 ''')
226 body = body.format(translation_table='\n'.join(translation_table),
227 name=self._name,
228 prototype=self._prototype)
Gilles Peskineccbc3182021-12-15 12:55:37 +0100229 return body
Jerry Yue78ee992021-09-22 15:42:14 +0800230
Jerry Yufe24d1c2022-04-11 21:04:47 +0800231
Jerry Yubfcfe742022-02-22 16:41:39 +0800232class SignatureAlgorithmDefinition:
233 """
234 Generate helper functions for signature algorithms.
235
236 It generates translation function from signature algorithm define to string.
237 Signature algorithm definition looks like:
238 #define MBEDTLS_TLS1_3_SIG_[ upper case signature algorithm ] [ value(hex) ]
239
240 Known limitation:
241 - the definitions SHOULD exist in same macro blocks.
242 """
243
244 @classmethod
245 def extract(cls, source_code, start=0, end=-1):
246 sig_alg_pattern = re.compile(r'#define\s+(?P<name>MBEDTLS_TLS1_3_SIG_\w+)\s+' +
247 r'(?P<value>0[xX][0-9a-fA-F]+)$',
248 re.MULTILINE | re.DOTALL)
249 matches = list(sig_alg_pattern.finditer(source_code, start, end))
250 if matches:
251 yield SignatureAlgorithmDefinition(source_code, definitions=matches)
252
253 def __init__(self, source_code, definitions=None):
254 if definitions is None:
255 definitions = []
256 assert isinstance(definitions, list) and definitions
257 self._definitions = definitions
258 self._source = source_code
259
260 def __repr__(self):
261 return 'SigAlgs({})'.format(self._definitions[0].span())
262
263 def span(self):
264 return self._definitions[0].span()
Jerry Yufe24d1c2022-04-11 21:04:47 +0800265
Jerry Yubfcfe742022-02-22 16:41:39 +0800266 def __str__(self):
267 """
268 Generate function for translating value to string
269 """
270 translation_table = []
271 for m in self._definitions:
272 name = m.groupdict()['name']
Paul Elliott4a496512022-07-08 19:59:09 +0100273 return_val = name[len('MBEDTLS_TLS1_3_SIG_'):].lower()
Jerry Yubfcfe742022-02-22 16:41:39 +0800274 translation_table.append(
Paul Elliott4a496512022-07-08 19:59:09 +0100275 ' case {}:\n return "{}";'.format(name, return_val))
Jerry Yubfcfe742022-02-22 16:41:39 +0800276
277 body = textwrap.dedent('''\
278 const char *mbedtls_ssl_sig_alg_to_str( uint16_t in )
279 {{
280 switch( in )
281 {{
282 {translation_table}
283 }};
284
Andrzej Kurek5c65c572022-04-13 14:28:52 -0400285 return "UNKNOWN";
Jerry Yubfcfe742022-02-22 16:41:39 +0800286 }}''')
287 body = body.format(translation_table='\n'.join(translation_table))
288 return body
Jerry Yue6369b02021-12-02 13:51:26 +0800289
Jerry Yufe24d1c2022-04-11 21:04:47 +0800290
291class NamedGroupDefinition:
292 """
293 Generate helper functions for named group
294
295 It generates translation function from named group define to string.
Jerry Yuab8bea22022-05-05 11:19:38 +0800296 Named group definition looks like:
Jerry Yufe24d1c2022-04-11 21:04:47 +0800297 #define MBEDTLS_SSL_IANA_TLS_GROUP_[ upper case named group ] [ value(hex) ]
298
299 Known limitation:
Jerry Yuab8bea22022-05-05 11:19:38 +0800300 - the definitions SHOULD exist in same macro blocks.
Jerry Yufe24d1c2022-04-11 21:04:47 +0800301 """
302
303 @classmethod
304 def extract(cls, source_code, start=0, end=-1):
Jerry Yuab8bea22022-05-05 11:19:38 +0800305 named_group_pattern = re.compile(r'#define\s+(?P<name>MBEDTLS_SSL_IANA_TLS_GROUP_\w+)\s+' +
306 r'(?P<value>0[xX][0-9a-fA-F]+)$',
307 re.MULTILINE | re.DOTALL)
308 matches = list(named_group_pattern.finditer(source_code, start, end))
Jerry Yufe24d1c2022-04-11 21:04:47 +0800309 if matches:
310 yield NamedGroupDefinition(source_code, definitions=matches)
311
312 def __init__(self, source_code, definitions=None):
313 if definitions is None:
314 definitions = []
315 assert isinstance(definitions, list) and definitions
316 self._definitions = definitions
317 self._source = source_code
318
319 def __repr__(self):
320 return 'NamedGroup({})'.format(self._definitions[0].span())
321
322 def span(self):
323 return self._definitions[0].span()
324
325 def __str__(self):
326 """
327 Generate function for translating value to string
328 """
329 translation_table = []
330 for m in self._definitions:
331 name = m.groupdict()['name']
332 iana_name = name[len('MBEDTLS_SSL_IANA_TLS_GROUP_'):].lower()
Paul Elliottfe9d43c2022-07-08 17:27:25 +0100333 translation_table.append(' case {}:\n return "{}";'.format(name, iana_name))
Jerry Yufe24d1c2022-04-11 21:04:47 +0800334
335 body = textwrap.dedent('''\
336 const char *mbedtls_ssl_named_group_to_str( uint16_t in )
337 {{
338 switch( in )
339 {{
340 {translation_table}
341 }};
342
343 return "UNKOWN";
344 }}''')
345 body = body.format(translation_table='\n'.join(translation_table))
346 return body
347
348
Jerry Yue78ee992021-09-22 15:42:14 +0800349OUTPUT_C_TEMPLATE = '''\
350/* Automatically generated by generate_ssl_debug_helpers.py. DO NOT EDIT. */
351
Gilles Peskine863b96a2021-12-16 10:04:58 +0100352/**
Dave Rodgmanb8f76942022-04-22 15:20:46 +0100353 * \\file ssl_debug_helpers_generated.c
Gilles Peskine863b96a2021-12-16 10:04:58 +0100354 *
Dave Rodgmanb8f76942022-04-22 15:20:46 +0100355 * \\brief Automatically generated helper functions for debugging
Gilles Peskine863b96a2021-12-16 10:04:58 +0100356 */
357/*
358 * Copyright The Mbed TLS Contributors
359 * SPDX-License-Identifier: Apache-2.0
360 *
361 * Licensed under the Apache License, Version 2.0 (the "License"); you may
362 * not use this file except in compliance with the License.
363 * You may obtain a copy of the License at
364 *
365 * http://www.apache.org/licenses/LICENSE-2.0
366 *
367 * Unless required by applicable law or agreed to in writing, software
368 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
369 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
370 * See the License for the specific language governing permissions and
371 * limitations under the License.
372 */
373
Jerry Yue78ee992021-09-22 15:42:14 +0800374#include "common.h"
375
376#if defined(MBEDTLS_DEBUG_C)
377
Gilles Peskine923d5c92021-12-15 12:56:54 +0100378#include "ssl_debug_helpers.h"
Jerry Yue78ee992021-09-22 15:42:14 +0800379
380{functions}
381
382#endif /* MBEDTLS_DEBUG_C */
383/* End of automatically generated file. */
384
385'''
386
Jerry Yue78ee992021-09-22 15:42:14 +0800387
Jerry Yue6369b02021-12-02 13:51:26 +0800388def generate_ssl_debug_helpers(output_directory, mbedtls_root):
Jerry Yue78ee992021-09-22 15:42:14 +0800389 """
390 Generate functions of debug helps
391 """
Jerry Yufe24d1c2022-04-11 21:04:47 +0800392 mbedtls_root = os.path.abspath(
393 mbedtls_root or build_tree.guess_mbedtls_root())
Jerry Yue6369b02021-12-02 13:51:26 +0800394 with open(os.path.join(mbedtls_root, 'include/mbedtls/ssl.h')) as f:
Jerry Yue78ee992021-09-22 15:42:14 +0800395 source_code = remove_c_comments(f.read())
396
397 definitions = dict()
Jerry Yubfcfe742022-02-22 16:41:39 +0800398 for start, instance in preprocess_c_source_code(source_code,
399 EnumDefinition,
Jerry Yufe24d1c2022-04-11 21:04:47 +0800400 SignatureAlgorithmDefinition,
401 NamedGroupDefinition):
Jerry Yue78ee992021-09-22 15:42:14 +0800402 if start in definitions:
403 continue
404 if isinstance(instance, EnumDefinition):
David Horstmann3be12712021-12-16 10:56:26 +0000405 definition = instance.generate_translation_function()
Jerry Yue78ee992021-09-22 15:42:14 +0800406 else:
407 definition = instance
Jerry Yue78ee992021-09-22 15:42:14 +0800408 definitions[start] = definition
Jerry Yue78ee992021-09-22 15:42:14 +0800409
Jerry Yue6369b02021-12-02 13:51:26 +0800410 function_definitions = [str(v) for _, v in sorted(definitions.items())]
Jerry Yue6369b02021-12-02 13:51:26 +0800411 if output_directory == sys.stdout:
Jerry Yue6369b02021-12-02 13:51:26 +0800412 sys.stdout.write(OUTPUT_C_TEMPLATE.format(
413 functions='\n'.join(function_definitions)))
414 else:
415 with open(os.path.join(output_directory, 'ssl_debug_helpers_generated.c'), 'w') as f:
416 f.write(OUTPUT_C_TEMPLATE.format(
417 functions='\n'.join(function_definitions)))
Jerry Yue78ee992021-09-22 15:42:14 +0800418
Jerry Yue78ee992021-09-22 15:42:14 +0800419
Jerry Yue6369b02021-12-02 13:51:26 +0800420def main():
421 """
422 Command line entry
423 """
424 parser = argparse.ArgumentParser()
Jerry Yu0cb2cf62021-12-10 14:21:27 +0800425 parser.add_argument('--mbedtls-root', nargs='?', default=None,
Jerry Yue6369b02021-12-02 13:51:26 +0800426 help='root directory of mbedtls source code')
427 parser.add_argument('output_directory', nargs='?',
Jerry Yu9817e3e2021-12-02 14:32:58 +0800428 default='library', help='source/header files location')
Jerry Yue78ee992021-09-22 15:42:14 +0800429
Jerry Yue6369b02021-12-02 13:51:26 +0800430 args = parser.parse_args()
431
432 generate_ssl_debug_helpers(args.output_directory, args.mbedtls_root)
433 return 0
Jerry Yue78ee992021-09-22 15:42:14 +0800434
435
436if __name__ == '__main__':
Jerry Yue6369b02021-12-02 13:51:26 +0800437 sys.exit(main())