#!/usr/bin/env python3 """Generate library/ssl_debug_helps_generated.c The code generated by this module includes debug helper functions that can not be implemented by fixed codes. """ # Copyright The Mbed TLS Contributors # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import sys import re import os import textwrap from mbedtls_dev import build_tree def remove_c_comments(string): """ Remove C style comments from input string """ string_pattern = r"(?P\".*?\"|\'.*?\')" comment_pattern = r"(?P/\*.*?\*/|//[^\r\n]*$)" pattern = re.compile(string_pattern + r'|' + comment_pattern, re.MULTILINE|re.DOTALL) def replacer(match): if match.lastgroup == 'comment': return "" return match.group() return pattern.sub(replacer, string) class CondDirectiveNotMatch(Exception): pass def preprocess_c_source_code(source, *classes): """ Simple preprocessor for C source code. Only processs condition directives without expanding them. Yield object accodring to the classes input. Most match firstly If there are directive pair does not match, raise CondDirectiveNotMatch. Assume source code does not include comments and compile pass. """ pattern = re.compile(r"^[ \t]*#[ \t]*" + r"(?P(if[ \t]|ifndef[ \t]|ifdef[ \t]|else|endif))" + r"[ \t]*(?P(.*\\\n)*.*$)", re.MULTILINE) stack = [] def _yield_objects(s, d, p, st, end): """ Output matched source piece """ nonlocal stack start_line, end_line = '', '' if stack: start_line = '#{} {}'.format(d, p) if d == 'if': end_line = '#endif /* {} */'.format(p) elif d == 'ifdef': end_line = '#endif /* defined({}) */'.format(p) else: end_line = '#endif /* !defined({}) */'.format(p) has_instance = False for cls in classes: for instance in cls.extract(s, st, end): if has_instance is False: has_instance = True yield pair_start, start_line yield instance.span()[0], instance if has_instance: yield start, end_line for match in pattern.finditer(source): directive = match.groupdict()['directive'].strip() param = match.groupdict()['param'] start, end = match.span() if directive in ('if', 'ifndef', 'ifdef'): stack.append((directive, param, start, end)) continue if not stack: raise CondDirectiveNotMatch() pair_directive, pair_param, pair_start, pair_end = stack.pop() yield from _yield_objects(source, pair_directive, pair_param, pair_end, start) if directive == 'endif': continue if pair_directive == 'if': directive = 'if' param = "!( {} )".format(pair_param) elif pair_directive == 'ifdef': directive = 'ifndef' param = pair_param else: directive = 'ifdef' param = pair_param stack.append((directive, param, start, end)) assert not stack, len(stack) class EnumDefinition: """ Generate helper functions around enumeration. Currently, it generate tranlation function from enum value to string. Enum definition looks like: [typedef] enum [prefix name] { [body] } [suffix name]; Known limitation: - the '}' and ';' SHOULD NOT exist in different macro blocks. Like ``` enum test { .... #if defined(A) .... }; #else .... }; #endif ``` """ @classmethod def extract(cls, source_code, start=0, end=-1): enum_pattern = re.compile(r'enum\s*(?P\w*)\s*' + r'{\s*(?P[^}]*)}' + r'\s*(?P\w*)\s*;', re.MULTILINE|re.DOTALL) for match in enum_pattern.finditer(source_code, start, end): yield EnumDefinition(source_code, span=match.span(), group=match.groupdict()) def __init__(self, source_code, span=None, group=None): assert isinstance(group, dict) prefix_name = group.get('prefix_name', None) suffix_name = group.get('suffix_name', None) body = group.get('body', None) assert prefix_name or suffix_name assert body assert span # If suffix_name exists, it is a typedef self._prototype = suffix_name if suffix_name else 'enum ' + prefix_name self._name = suffix_name if suffix_name else prefix_name self._body = body self._source = source_code self._span = span def __repr__(self): return 'Enum({},{})'.format(self._name, self._span) def __str__(self): return repr(self) def span(self): return self._span def generate_tranlation_function(self): """ Generate function for translating value to string """ translation_table = [] for line in self._body.splitlines(): if line.strip().startswith('#'): # Preprocess directive, keep it in table translation_table.append(line.strip()) continue if not line.strip(): continue for field in line.strip().split(','): if not field.strip(): continue member = field.strip().split()[0] translation_table.append( '{space}[{member}] = "{member}",'.format(member=member, space=' '*8) ) body = textwrap.dedent('''\ const char *{name}_str( {prototype} in ) {{ const char * in_to_str[]= {{ {translation_table} }}; if( in > ( sizeof( in_to_str )/sizeof( in_to_str[0]) - 1 ) || in_to_str[ in ] == NULL ) {{ return "UNKOWN_VAULE"; }} return in_to_str[ in ]; }} ''') body = body.format(translation_table='\n'.join(translation_table), name=self._name, prototype=self._prototype) prototype = 'const char *{name}_str( {prototype} in );\n' prototype = prototype.format(name=self._name, prototype=self._prototype) return body, prototype OUTPUT_C_TEMPLATE = '''\ /* Automatically generated by generate_ssl_debug_helpers.py. DO NOT EDIT. */ #include "common.h" #if defined(MBEDTLS_DEBUG_C) #include "ssl_debug_helpers_generated.h" {functions} #endif /* MBEDTLS_DEBUG_C */ /* End of automatically generated file. */ ''' OUTPUT_H_TEMPLATE = '''\ /* Automatically generated by generate_ssl_debug_helpers.py. DO NOT EDIT. */ #ifndef MBEDTLS_SSL_DEBUG_HELPERS_H #define MBEDTLS_SSL_DEBUG_HELPERS_H #include "common.h" #if defined(MBEDTLS_DEBUG_C) #include "mbedtls/ssl.h" #include "ssl_misc.h" {functions} #endif /* MBEDTLS_DEBUG_C */ #endif /* SSL_DEBUG_HELPERS_H */ /* End of automatically generated file. */ ''' def generate_ssl_debug_helpers(target_dir): """ Generate functions of debug helps """ with open('include/mbedtls/ssl.h') as f: source_code = remove_c_comments(f.read()) definitions = dict() prototypes = dict() for start, instance in preprocess_c_source_code(source_code, EnumDefinition): if start in definitions: continue if isinstance(instance, EnumDefinition): definition, prototype = instance.generate_tranlation_function() else: definition = instance prototype = instance definitions[start] = definition prototypes[start] = prototype functions = [str(v) for _, v in sorted(definitions.items())] with open(os.path.join(target_dir, 'ssl_debug_helpers_generated.c'), 'w') as f: f.write(OUTPUT_C_TEMPLATE.format(functions='\n'.join(functions))) functions = [str(v) for _, v in sorted(prototypes.items())] with open(os.path.join(target_dir, 'ssl_debug_helpers_generated.h'), 'w') as f: f.write(OUTPUT_H_TEMPLATE.format(functions='\n'.join(functions))) if __name__ == '__main__': build_tree.chdir_to_root() OUTPUT_FILE_DIR = sys.argv[1] if len(sys.argv) == 2 else "library" generate_ssl_debug_helpers(OUTPUT_FILE_DIR)