quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

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())