1#!/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()) 415