mirror of
				https://github.com/Mbed-TLS/mbedtls.git
				synced 2025-10-30 10:45:34 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			415 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			415 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env python3
 | |
| 
 | |
| """Generate library/ssl_debug_helpers_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 OR GPL-2.0-or-later
 | |
| 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 processes 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_translation_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}case {member}:\n{space}    return "{member}";'
 | |
|                     .format(member=member, space=' '*8)
 | |
|                 )
 | |
| 
 | |
|         body = textwrap.dedent('''\
 | |
|             const char *{name}_str( {prototype} in )
 | |
|             {{
 | |
|                 switch (in) {{
 | |
|             {translation_table}
 | |
|                     default:
 | |
|                         return "UNKNOWN_VALUE";
 | |
|                 }}
 | |
|             }}
 | |
|                     ''')
 | |
|         body = body.format(translation_table='\n'.join(translation_table),
 | |
|                            name=self._name,
 | |
|                            prototype=self._prototype)
 | |
|         return body
 | |
| 
 | |
| 
 | |
| class SignatureAlgorithmDefinition:
 | |
|     """
 | |
|         Generate helper functions for signature algorithms.
 | |
| 
 | |
|         It generates translation function from signature algorithm define to string.
 | |
|         Signature algorithm definition looks like:
 | |
|         #define MBEDTLS_TLS1_3_SIG_[ upper case signature algorithm ] [ value(hex) ]
 | |
| 
 | |
|         Known limitation:
 | |
|         - the definitions SHOULD  exist in same macro blocks.
 | |
|     """
 | |
| 
 | |
|     @classmethod
 | |
|     def extract(cls, source_code, start=0, end=-1):
 | |
|         sig_alg_pattern = re.compile(r'#define\s+(?P<name>MBEDTLS_TLS1_3_SIG_\w+)\s+' +
 | |
|                                      r'(?P<value>0[xX][0-9a-fA-F]+)$',
 | |
|                                      re.MULTILINE | re.DOTALL)
 | |
|         matches = list(sig_alg_pattern.finditer(source_code, start, end))
 | |
|         if matches:
 | |
|             yield SignatureAlgorithmDefinition(source_code, definitions=matches)
 | |
| 
 | |
|     def __init__(self, source_code, definitions=None):
 | |
|         if definitions is None:
 | |
|             definitions = []
 | |
|         assert isinstance(definitions, list) and definitions
 | |
|         self._definitions = definitions
 | |
|         self._source = source_code
 | |
| 
 | |
|     def __repr__(self):
 | |
|         return 'SigAlgs({})'.format(self._definitions[0].span())
 | |
| 
 | |
|     def span(self):
 | |
|         return self._definitions[0].span()
 | |
| 
 | |
|     def __str__(self):
 | |
|         """
 | |
|             Generate function for translating value to string
 | |
|         """
 | |
|         translation_table = []
 | |
|         for m in self._definitions:
 | |
|             name = m.groupdict()['name']
 | |
|             return_val = name[len('MBEDTLS_TLS1_3_SIG_'):].lower()
 | |
|             translation_table.append(
 | |
|                 '    case {}:\n        return "{}";'.format(name, return_val))
 | |
| 
 | |
|         body = textwrap.dedent('''\
 | |
|             const char *mbedtls_ssl_sig_alg_to_str( uint16_t in )
 | |
|             {{
 | |
|                 switch( in )
 | |
|                 {{
 | |
|             {translation_table}
 | |
|                 }};
 | |
| 
 | |
|                 return "UNKNOWN";
 | |
|             }}''')
 | |
|         body = body.format(translation_table='\n'.join(translation_table))
 | |
|         return body
 | |
| 
 | |
| 
 | |
| class NamedGroupDefinition:
 | |
|     """
 | |
|         Generate helper functions for named group
 | |
| 
 | |
|         It generates translation function from named group define to string.
 | |
|         Named group definition looks like:
 | |
|         #define MBEDTLS_SSL_IANA_TLS_GROUP_[ upper case named group ] [ value(hex) ]
 | |
| 
 | |
|         Known limitation:
 | |
|         - the definitions SHOULD exist in same macro blocks.
 | |
|     """
 | |
| 
 | |
|     @classmethod
 | |
|     def extract(cls, source_code, start=0, end=-1):
 | |
|         named_group_pattern = re.compile(r'#define\s+(?P<name>MBEDTLS_SSL_IANA_TLS_GROUP_\w+)\s+' +
 | |
|                                          r'(?P<value>0[xX][0-9a-fA-F]+)$',
 | |
|                                          re.MULTILINE | re.DOTALL)
 | |
|         matches = list(named_group_pattern.finditer(source_code, start, end))
 | |
|         if matches:
 | |
|             yield NamedGroupDefinition(source_code, definitions=matches)
 | |
| 
 | |
|     def __init__(self, source_code, definitions=None):
 | |
|         if definitions is None:
 | |
|             definitions = []
 | |
|         assert isinstance(definitions, list) and definitions
 | |
|         self._definitions = definitions
 | |
|         self._source = source_code
 | |
| 
 | |
|     def __repr__(self):
 | |
|         return 'NamedGroup({})'.format(self._definitions[0].span())
 | |
| 
 | |
|     def span(self):
 | |
|         return self._definitions[0].span()
 | |
| 
 | |
|     def __str__(self):
 | |
|         """
 | |
|             Generate function for translating value to string
 | |
|         """
 | |
|         translation_table = []
 | |
|         for m in self._definitions:
 | |
|             name = m.groupdict()['name']
 | |
|             iana_name = name[len('MBEDTLS_SSL_IANA_TLS_GROUP_'):].lower()
 | |
|             translation_table.append('    case {}:\n        return "{}";'.format(name, iana_name))
 | |
| 
 | |
|         body = textwrap.dedent('''\
 | |
|             const char *mbedtls_ssl_named_group_to_str( uint16_t in )
 | |
|             {{
 | |
|                 switch( in )
 | |
|                 {{
 | |
|             {translation_table}
 | |
|                 }};
 | |
| 
 | |
|                 return "UNKOWN";
 | |
|             }}''')
 | |
|         body = body.format(translation_table='\n'.join(translation_table))
 | |
|         return body
 | |
| 
 | |
| 
 | |
| OUTPUT_C_TEMPLATE = '''\
 | |
| /* Automatically generated by generate_ssl_debug_helpers.py. DO NOT EDIT. */
 | |
| 
 | |
| /**
 | |
|  * \\file ssl_debug_helpers_generated.c
 | |
|  *
 | |
|  * \\brief Automatically generated helper functions for debugging
 | |
|  */
 | |
| /*
 | |
|  *  Copyright The Mbed TLS Contributors
 | |
|  *  SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
 | |
|  *
 | |
|  */
 | |
| 
 | |
| #include "common.h"
 | |
| 
 | |
| #if defined(MBEDTLS_DEBUG_C)
 | |
| 
 | |
| #include "ssl_debug_helpers.h"
 | |
| 
 | |
| {functions}
 | |
| 
 | |
| #endif /* MBEDTLS_DEBUG_C */
 | |
| /* End of automatically generated file. */
 | |
| 
 | |
| '''
 | |
| 
 | |
| 
 | |
| def generate_ssl_debug_helpers(output_directory, mbedtls_root):
 | |
|     """
 | |
|         Generate functions of debug helps
 | |
|     """
 | |
|     mbedtls_root = os.path.abspath(
 | |
|         mbedtls_root or build_tree.guess_mbedtls_root())
 | |
|     with open(os.path.join(mbedtls_root, 'include/mbedtls/ssl.h')) as f:
 | |
|         source_code = remove_c_comments(f.read())
 | |
| 
 | |
|     definitions = dict()
 | |
|     for start, instance in preprocess_c_source_code(source_code,
 | |
|                                                     EnumDefinition,
 | |
|                                                     SignatureAlgorithmDefinition,
 | |
|                                                     NamedGroupDefinition):
 | |
|         if start in definitions:
 | |
|             continue
 | |
|         if isinstance(instance, EnumDefinition):
 | |
|             definition = instance.generate_translation_function()
 | |
|         else:
 | |
|             definition = instance
 | |
|         definitions[start] = definition
 | |
| 
 | |
|     function_definitions = [str(v) for _, v in sorted(definitions.items())]
 | |
|     if output_directory == sys.stdout:
 | |
|         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)))
 | |
| 
 | |
| 
 | |
| def main():
 | |
|     """
 | |
|     Command line entry
 | |
|     """
 | |
|     parser = argparse.ArgumentParser()
 | |
|     parser.add_argument('--mbedtls-root', nargs='?', default=None,
 | |
|                         help='root directory of mbedtls source code')
 | |
|     parser.add_argument('output_directory', nargs='?',
 | |
|                         default='library', 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())
 |