1#!/usr/bin/env python3 2 3"""Generate library/ssl_debug_helps_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 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 28import argparse 29from mbedtls_dev import build_tree 30 31 32def 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, 39 re.MULTILINE | re.DOTALL) 40 41 def replacer(match): 42 if match.lastgroup == 'comment': 43 return "" 44 return match.group() 45 return pattern.sub(replacer, string) 46 47 48class CondDirectiveNotMatch(Exception): 49 pass 50 51 52def preprocess_c_source_code(source, *classes): 53 """ 54 Simple preprocessor for C source code. 55 56 Only processses condition directives without expanding them. 57 Yield object according to the classes input. Most match firstly 58 59 If the directive pair does not match , raise CondDirectiveNotMatch. 60 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 91 yield instance.span()[0], instance 92 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 132class EnumDefinition: 133 """ 134 Generate helper functions around enumeration. 135 136 Currently, it generate translation function from enum value to string. 137 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*;', 160 re.MULTILINE | re.DOTALL) 161 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 191 def generate_tranlation_function(self): 192 """ 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( 212 '{space}[{member}] = "{member}",'.format(member=member, 213 space=' '*8) 214 ) 215 216 body = textwrap.dedent('''\ 217 const char *{name}_str( {prototype} in ) 218 {{ 219 const char * in_to_str[]= 220 {{ 221 {translation_table} 222 }}; 223 224 if( in > ( sizeof( in_to_str )/sizeof( in_to_str[0]) - 1 ) || 225 in_to_str[ in ] == NULL ) 226 {{ 227 return "UNKOWN_VAULE"; 228 }} 229 return in_to_str[ in ]; 230 }} 231 ''') 232 body = body.format(translation_table='\n'.join(translation_table), 233 name=self._name, 234 prototype=self._prototype) 235 prototype = 'const char *{name}_str( {prototype} in );\n' 236 prototype = prototype.format(name=self._name, 237 prototype=self._prototype) 238 return body, prototype 239 240 241OUTPUT_C_TEMPLATE = '''\ 242/* Automatically generated by generate_ssl_debug_helpers.py. DO NOT EDIT. */ 243 244#include "common.h" 245 246#if defined(MBEDTLS_DEBUG_C) 247 248#include "ssl_debug_helpers_generated.h" 249 250{functions} 251 252#endif /* MBEDTLS_DEBUG_C */ 253/* End of automatically generated file. */ 254 255''' 256 257OUTPUT_H_TEMPLATE = '''\ 258/* Automatically generated by generate_ssl_debug_helpers.py. DO NOT EDIT. */ 259#ifndef MBEDTLS_SSL_DEBUG_HELPERS_H 260#define MBEDTLS_SSL_DEBUG_HELPERS_H 261 262#include "common.h" 263 264#if defined(MBEDTLS_DEBUG_C) 265 266#include "mbedtls/ssl.h" 267#include "ssl_misc.h" 268 269{functions} 270 271#endif /* MBEDTLS_DEBUG_C */ 272 273#endif /* SSL_DEBUG_HELPERS_H */ 274 275/* End of automatically generated file. */ 276 277''' 278 279 280def generate_ssl_debug_helpers(output_directory, mbedtls_root): 281 """ 282 Generate functions of debug helps 283 """ 284 mbedtls_root = os.path.abspath(mbedtls_root or build_tree.guess_mbedtls_root()) 285 with open(os.path.join(mbedtls_root, 'include/mbedtls/ssl.h')) as f: 286 source_code = remove_c_comments(f.read()) 287 288 definitions = dict() 289 prototypes = dict() 290 for start, instance in preprocess_c_source_code(source_code, EnumDefinition): 291 if start in definitions: 292 continue 293 if isinstance(instance, EnumDefinition): 294 definition, prototype = instance.generate_tranlation_function() 295 else: 296 definition = instance 297 prototype = instance 298 definitions[start] = definition 299 prototypes[start] = prototype 300 301 function_definitions = [str(v) for _, v in sorted(definitions.items())] 302 function_prototypes = [str(v) for _, v in sorted(prototypes.items())] 303 if output_directory == sys.stdout: 304 sys.stdout.write(OUTPUT_H_TEMPLATE.format( 305 functions='\n'.join(function_prototypes))) 306 sys.stdout.write(OUTPUT_C_TEMPLATE.format( 307 functions='\n'.join(function_definitions))) 308 else: 309 with open(os.path.join(output_directory, 'ssl_debug_helpers_generated.c'), 'w') as f: 310 f.write(OUTPUT_C_TEMPLATE.format( 311 functions='\n'.join(function_definitions))) 312 313 with open(os.path.join(output_directory, 'ssl_debug_helpers_generated.h'), 'w') as f: 314 f.write(OUTPUT_H_TEMPLATE.format( 315 functions='\n'.join(function_prototypes))) 316 317 318def main(): 319 """ 320 Command line entry 321 """ 322 parser = argparse.ArgumentParser() 323 parser.add_argument('--mbedtls-root', nargs='?', default=None, 324 help='root directory of mbedtls source code') 325 parser.add_argument('output_directory', nargs='?', 326 default='library', help='source/header files location') 327 328 args = parser.parse_args() 329 330 generate_ssl_debug_helpers(args.output_directory, args.mbedtls_root) 331 return 0 332 333 334if __name__ == '__main__': 335 sys.exit(main()) 336