aboutsummaryrefslogtreecommitdiff
path: root/scripts/generate_ssl_debug_helpers.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/generate_ssl_debug_helpers.py')
-rwxr-xr-xscripts/generate_ssl_debug_helpers.py443
1 files changed, 443 insertions, 0 deletions
diff --git a/scripts/generate_ssl_debug_helpers.py b/scripts/generate_ssl_debug_helpers.py
new file mode 100755
index 000000000..3127afcee
--- /dev/null
+++ b/scripts/generate_ssl_debug_helpers.py
@@ -0,0 +1,443 @@
+#!/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
+#
+# 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 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}[{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 "UNKNOWN_VALUE";
+ }}
+ return in_to_str[ in ];
+ }}
+ ''')
+ 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
+ *
+ * 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.
+ */
+
+#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())