generate_ssl_debug_helpers.py (13226B)
1 #!/usr/bin/env python3 2 3 """Generate library/ssl_debug_helpers_generated.c 4 5 The code generated by this module includes debug helper functions that can not be 6 implemented 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 12 import sys 13 import re 14 import os 15 import textwrap 16 import argparse 17 18 from mbedtls_framework import build_tree 19 20 21 def remove_c_comments(string): 22 """ 23 Remove C style comments from input string 24 """ 25 string_pattern = r"(?P<string>\".*?\"|\'.*?\')" 26 comment_pattern = r"(?P<comment>/\*.*?\*/|//[^\r\n]*$)" 27 pattern = re.compile(string_pattern + r'|' + comment_pattern, 28 re.MULTILINE | re.DOTALL) 29 30 def replacer(match): 31 if match.lastgroup == 'comment': 32 return "" 33 return match.group() 34 return pattern.sub(replacer, string) 35 36 37 class CondDirectiveNotMatch(Exception): 38 pass 39 40 41 def preprocess_c_source_code(source, *classes): 42 """ 43 Simple preprocessor for C source code. 44 45 Only processes condition directives without expanding them. 46 Yield object according to the classes input. Most match firstly 47 48 If the directive pair does not match , raise CondDirectiveNotMatch. 49 50 Assume source code does not include comments and compile pass. 51 52 """ 53 54 pattern = re.compile(r"^[ \t]*#[ \t]*" + 55 r"(?P<directive>(if[ \t]|ifndef[ \t]|ifdef[ \t]|else|endif))" + 56 r"[ \t]*(?P<param>(.*\\\n)*.*$)", 57 re.MULTILINE) 58 stack = [] 59 60 def _yield_objects(s, d, p, st, end): 61 """ 62 Output matched source piece 63 """ 64 nonlocal stack 65 start_line, end_line = '', '' 66 if stack: 67 start_line = '#{} {}'.format(d, p) 68 if d == 'if': 69 end_line = '#endif /* {} */'.format(p) 70 elif d == 'ifdef': 71 end_line = '#endif /* defined({}) */'.format(p) 72 else: 73 end_line = '#endif /* !defined({}) */'.format(p) 74 has_instance = False 75 for cls in classes: 76 for instance in cls.extract(s, st, end): 77 if has_instance is False: 78 has_instance = True 79 yield pair_start, start_line 80 yield instance.span()[0], instance 81 if has_instance: 82 yield start, end_line 83 84 for match in pattern.finditer(source): 85 86 directive = match.groupdict()['directive'].strip() 87 param = match.groupdict()['param'] 88 start, end = match.span() 89 90 if directive in ('if', 'ifndef', 'ifdef'): 91 stack.append((directive, param, start, end)) 92 continue 93 94 if not stack: 95 raise CondDirectiveNotMatch() 96 97 pair_directive, pair_param, pair_start, pair_end = stack.pop() 98 yield from _yield_objects(source, 99 pair_directive, 100 pair_param, 101 pair_end, 102 start) 103 104 if directive == 'endif': 105 continue 106 107 if pair_directive == 'if': 108 directive = 'if' 109 param = "!( {} )".format(pair_param) 110 elif pair_directive == 'ifdef': 111 directive = 'ifndef' 112 param = pair_param 113 else: 114 directive = 'ifdef' 115 param = pair_param 116 117 stack.append((directive, param, start, end)) 118 assert not stack, len(stack) 119 120 121 class EnumDefinition: 122 """ 123 Generate helper functions around enumeration. 124 125 Currently, it generate translation function from enum value to string. 126 Enum definition looks like: 127 [typedef] enum [prefix name] { [body] } [suffix name]; 128 129 Known limitation: 130 - the '}' and ';' SHOULD NOT exist in different macro blocks. Like 131 ``` 132 enum test { 133 .... 134 #if defined(A) 135 .... 136 }; 137 #else 138 .... 139 }; 140 #endif 141 ``` 142 """ 143 144 @classmethod 145 def extract(cls, source_code, start=0, end=-1): 146 enum_pattern = re.compile(r'enum\s*(?P<prefix_name>\w*)\s*' + 147 r'{\s*(?P<body>[^}]*)}' + 148 r'\s*(?P<suffix_name>\w*)\s*;', 149 re.MULTILINE | re.DOTALL) 150 151 for match in enum_pattern.finditer(source_code, start, end): 152 yield EnumDefinition(source_code, 153 span=match.span(), 154 group=match.groupdict()) 155 156 def __init__(self, source_code, span=None, group=None): 157 assert isinstance(group, dict) 158 prefix_name = group.get('prefix_name', None) 159 suffix_name = group.get('suffix_name', None) 160 body = group.get('body', None) 161 assert prefix_name or suffix_name 162 assert body 163 assert span 164 # If suffix_name exists, it is a typedef 165 self._prototype = suffix_name if suffix_name else 'enum ' + prefix_name 166 self._name = suffix_name if suffix_name else prefix_name 167 self._body = body 168 self._source = source_code 169 self._span = span 170 171 def __repr__(self): 172 return 'Enum({},{})'.format(self._name, self._span) 173 174 def __str__(self): 175 return repr(self) 176 177 def span(self): 178 return self._span 179 180 def generate_translation_function(self): 181 """ 182 Generate function for translating value to string 183 """ 184 translation_table = [] 185 186 for line in self._body.splitlines(): 187 188 if line.strip().startswith('#'): 189 # Preprocess directive, keep it in table 190 translation_table.append(line.strip()) 191 continue 192 193 if not line.strip(): 194 continue 195 196 for field in line.strip().split(','): 197 if not field.strip(): 198 continue 199 member = field.strip().split()[0] 200 translation_table.append( 201 '{space}case {member}:\n{space} return "{member}";' 202 .format(member=member, space=' '*8) 203 ) 204 205 body = textwrap.dedent('''\ 206 const char *{name}_str( {prototype} in ) 207 {{ 208 switch (in) {{ 209 {translation_table} 210 default: 211 return "UNKNOWN_VALUE"; 212 }} 213 }} 214 ''') 215 body = body.format(translation_table='\n'.join(translation_table), 216 name=self._name, 217 prototype=self._prototype) 218 return body 219 220 221 class SignatureAlgorithmDefinition: 222 """ 223 Generate helper functions for signature algorithms. 224 225 It generates translation function from signature algorithm define to string. 226 Signature algorithm definition looks like: 227 #define MBEDTLS_TLS1_3_SIG_[ upper case signature algorithm ] [ value(hex) ] 228 229 Known limitation: 230 - the definitions SHOULD exist in same macro blocks. 231 """ 232 233 @classmethod 234 def extract(cls, source_code, start=0, end=-1): 235 sig_alg_pattern = re.compile(r'#define\s+(?P<name>MBEDTLS_TLS1_3_SIG_\w+)\s+' + 236 r'(?P<value>0[xX][0-9a-fA-F]+)$', 237 re.MULTILINE | re.DOTALL) 238 matches = list(sig_alg_pattern.finditer(source_code, start, end)) 239 if matches: 240 yield SignatureAlgorithmDefinition(source_code, definitions=matches) 241 242 def __init__(self, source_code, definitions=None): 243 if definitions is None: 244 definitions = [] 245 assert isinstance(definitions, list) and definitions 246 self._definitions = definitions 247 self._source = source_code 248 249 def __repr__(self): 250 return 'SigAlgs({})'.format(self._definitions[0].span()) 251 252 def span(self): 253 return self._definitions[0].span() 254 255 def __str__(self): 256 """ 257 Generate function for translating value to string 258 """ 259 translation_table = [] 260 for m in self._definitions: 261 name = m.groupdict()['name'] 262 return_val = name[len('MBEDTLS_TLS1_3_SIG_'):].lower() 263 translation_table.append( 264 ' case {}:\n return "{}";'.format(name, return_val)) 265 266 body = textwrap.dedent('''\ 267 const char *mbedtls_ssl_sig_alg_to_str( uint16_t in ) 268 {{ 269 switch( in ) 270 {{ 271 {translation_table} 272 }}; 273 274 return "UNKNOWN"; 275 }}''') 276 body = body.format(translation_table='\n'.join(translation_table)) 277 return body 278 279 280 class NamedGroupDefinition: 281 """ 282 Generate helper functions for named group 283 284 It generates translation function from named group define to string. 285 Named group definition looks like: 286 #define MBEDTLS_SSL_IANA_TLS_GROUP_[ upper case named group ] [ value(hex) ] 287 288 Known limitation: 289 - the definitions SHOULD exist in same macro blocks. 290 """ 291 292 @classmethod 293 def extract(cls, source_code, start=0, end=-1): 294 named_group_pattern = re.compile(r'#define\s+(?P<name>MBEDTLS_SSL_IANA_TLS_GROUP_\w+)\s+' + 295 r'(?P<value>0[xX][0-9a-fA-F]+)$', 296 re.MULTILINE | re.DOTALL) 297 matches = list(named_group_pattern.finditer(source_code, start, end)) 298 if matches: 299 yield NamedGroupDefinition(source_code, definitions=matches) 300 301 def __init__(self, source_code, definitions=None): 302 if definitions is None: 303 definitions = [] 304 assert isinstance(definitions, list) and definitions 305 self._definitions = definitions 306 self._source = source_code 307 308 def __repr__(self): 309 return 'NamedGroup({})'.format(self._definitions[0].span()) 310 311 def span(self): 312 return self._definitions[0].span() 313 314 def __str__(self): 315 """ 316 Generate function for translating value to string 317 """ 318 translation_table = [] 319 for m in self._definitions: 320 name = m.groupdict()['name'] 321 iana_name = name[len('MBEDTLS_SSL_IANA_TLS_GROUP_'):].lower() 322 translation_table.append(' case {}:\n return "{}";'.format(name, iana_name)) 323 324 body = textwrap.dedent('''\ 325 const char *mbedtls_ssl_named_group_to_str( uint16_t in ) 326 {{ 327 switch( in ) 328 {{ 329 {translation_table} 330 }}; 331 332 return "UNKNOWN"; 333 }}''') 334 body = body.format(translation_table='\n'.join(translation_table)) 335 return body 336 337 338 OUTPUT_C_TEMPLATE = '''\ 339 /* Automatically generated by generate_ssl_debug_helpers.py. DO NOT EDIT. */ 340 341 /** 342 * \\file ssl_debug_helpers_generated.c 343 * 344 * \\brief Automatically generated helper functions for debugging 345 */ 346 /* 347 * Copyright The Mbed TLS Contributors 348 * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later 349 * 350 */ 351 352 #include "ssl_misc.h" 353 354 #if defined(MBEDTLS_DEBUG_C) 355 356 #include "ssl_debug_helpers.h" 357 358 {functions} 359 360 #endif /* MBEDTLS_DEBUG_C */ 361 /* End of automatically generated file. */ 362 363 ''' 364 365 366 def generate_ssl_debug_helpers(output_directory, mbedtls_root): 367 """ 368 Generate functions of debug helps 369 """ 370 mbedtls_root = os.path.abspath( 371 mbedtls_root or build_tree.guess_mbedtls_root()) 372 with open(os.path.join(mbedtls_root, 'include/mbedtls/ssl.h')) as f: 373 source_code = remove_c_comments(f.read()) 374 375 definitions = dict() 376 for start, instance in preprocess_c_source_code(source_code, 377 EnumDefinition, 378 SignatureAlgorithmDefinition, 379 NamedGroupDefinition): 380 if start in definitions: 381 continue 382 if isinstance(instance, EnumDefinition): 383 definition = instance.generate_translation_function() 384 else: 385 definition = instance 386 definitions[start] = definition 387 388 function_definitions = [str(v) for _, v in sorted(definitions.items())] 389 if output_directory == sys.stdout: 390 sys.stdout.write(OUTPUT_C_TEMPLATE.format( 391 functions='\n'.join(function_definitions))) 392 else: 393 with open(os.path.join(output_directory, 'ssl_debug_helpers_generated.c'), 'w') as f: 394 f.write(OUTPUT_C_TEMPLATE.format( 395 functions='\n'.join(function_definitions))) 396 397 398 def main(): 399 """ 400 Command line entry 401 """ 402 parser = argparse.ArgumentParser() 403 parser.add_argument('--mbedtls-root', nargs='?', default=None, 404 help='root directory of mbedtls source code') 405 parser.add_argument('output_directory', nargs='?', 406 default='library', help='source/header files location') 407 408 args = parser.parse_args() 409 410 generate_ssl_debug_helpers(args.output_directory, args.mbedtls_root) 411 return 0 412 413 414 if __name__ == '__main__': 415 sys.exit(main())