| #!/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 |
| import argparse |
| from mbedtls_dev import build_tree |
| |
| |
| def remove_c_comments(string): |
| """ |
| Remove C style comments from input string |
| """ |
| string_pattern = r"(?P<string>\".*?\"|\'.*?\')" |
| comment_pattern = r"(?P<comment>/\*.*?\*/|//[^\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 processses condition directives without expanding them. |
| Yield object according to the classes input. Most match firstly |
| |
| If the 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<directive>(if[ \t]|ifndef[ \t]|ifdef[ \t]|else|endif))" + |
| r"[ \t]*(?P<param>(.*\\\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 translation 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<prefix_name>\w*)\s*' + |
| r'{\s*(?P<body>[^}]*)}' + |
| r'\s*(?P<suffix_name>\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(output_directory, mbedtls_root): |
| """ |
| Generate functions of debug helps |
| """ |
| with open(os.path.join(mbedtls_root, '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 |
| |
| function_definitions = [str(v) for _, v in sorted(definitions.items())] |
| function_prototypes = [str(v) for _, v in sorted(prototypes.items())] |
| if output_directory == sys.stdout: |
| sys.stdout.write(OUTPUT_H_TEMPLATE.format( |
| functions='\n'.join(function_prototypes))) |
| sys.stdout.write(OUTPUT_C_TEMPLATE.format( |
| functions='\n'.join(function_definitions))) |
| else: |
| with open(os.path.join(output_directory, 'ssl_debug_helpers_generated.c'), 'w') as f: |
| f.write(OUTPUT_C_TEMPLATE.format( |
| functions='\n'.join(function_definitions))) |
| |
| with open(os.path.join(output_directory, 'ssl_debug_helpers_generated.h'), 'w') as f: |
| f.write(OUTPUT_H_TEMPLATE.format( |
| functions='\n'.join(function_prototypes))) |
| |
| |
| def main(): |
| """ |
| Command line entry |
| """ |
| parser = argparse.ArgumentParser() |
| parser.add_argument('--mbedtls-root', nargs='?', default=build_tree.guess_mbedtls_root(), |
| help='root directory of mbedtls source code') |
| parser.add_argument('output_directory', nargs='?', |
| default=sys.stdout, help='source/header files location') |
| |
| args = parser.parse_args() |
| |
| generate_ssl_debug_helpers(args.output_directory, args.mbedtls_root) |
| return 0 |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |