add enum value to string helpers

Only add helpers for enum in `ssl.h`.

Signed-off-by: Jerry Yu <jerry.h.yu@arm.com>
diff --git a/library/.gitignore b/library/.gitignore
index 6fde1f5..f6619d2 100644
--- a/library/.gitignore
+++ b/library/.gitignore
@@ -6,3 +6,5 @@
 # Automatically generated files
 /error.c
 /version_features.c
+/ssl_debug_helpers_generated.c
+/ssl_debug_helpers_generated.h
diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt
index caac2d5..d04f9fd 100644
--- a/library/CMakeLists.txt
+++ b/library/CMakeLists.txt
@@ -77,6 +77,7 @@
     sha1.c
     sha256.c
     sha512.c
+    ssl_debug_helpers_generated.c
     threading.c
     timing.c
     version.c
diff --git a/library/Makefile b/library/Makefile
index bd116be..2af9044 100644
--- a/library/Makefile
+++ b/library/Makefile
@@ -24,6 +24,12 @@
 
 PERL ?= perl
 
+ifdef WINDOWS
+PYTHON ?= python
+else
+PYTHON ?= $(shell if type python3 >/dev/null 2>/dev/null; then echo python3; else echo python; fi)
+endif
+
 # if were running on Windows build for Windows
 ifdef WINDOWS
 WINDOWS_BUILD=1
@@ -136,6 +142,7 @@
 	     sha1.o \
 	     sha256.o \
 	     sha512.o \
+	     ssl_debug_helpers_generated.o \
 	     threading.o \
 	     timing.o \
 	     version.o \
@@ -281,7 +288,7 @@
 	$(CC) $(LOCAL_CFLAGS) $(CFLAGS) -o $@ -c $<
 
 .PHONY: generated_files
-GENERATED_FILES = error.c version_features.c
+GENERATED_FILES = error.c version_features.c ssl_debug_helpers_generated.c
 generated_files: $(GENERATED_FILES)
 
 error.c: ../scripts/generate_errors.pl
@@ -291,6 +298,12 @@
 	echo "  Gen   $@"
 	$(PERL) ../scripts/generate_errors.pl
 
+ssl_debug_helpers_generated.c: ../scripts/generate_ssl_debug_helpers.py
+ssl_debug_helpers_generated.c: $(filter-out %config%,$(wildcard ../include/mbedtls/*.h))
+ssl_debug_helpers_generated.c:
+	echo "  Gen   $@"
+	$(PYTHON) ../scripts/generate_ssl_debug_helpers.py
+
 version_features.c: ../scripts/generate_features.pl
 version_features.c: ../scripts/data_files/version_features.fmt
 ## The generated file only depends on the options that are present in mbedtls_config.h,
diff --git a/library/ssl_tls13_client.c b/library/ssl_tls13_client.c
index 1874d4f..5968501 100644
--- a/library/ssl_tls13_client.c
+++ b/library/ssl_tls13_client.c
@@ -34,6 +34,7 @@
 #include "ssl_misc.h"
 #include "ecdh_misc.h"
 #include "ssl_tls13_keys.h"
+#include "ssl_debug_helpers_generated.h"
 
 /* Write extensions */
 
diff --git a/library/ssl_tls13_server.c b/library/ssl_tls13_server.c
index 3018ecb..e9c74ca 100644
--- a/library/ssl_tls13_server.c
+++ b/library/ssl_tls13_server.c
@@ -26,6 +26,7 @@
 #include "mbedtls/debug.h"
 
 #include "ssl_misc.h"
+#include "ssl_debug_helpers_generated.h"
 
 int mbedtls_ssl_tls13_handshake_server_step( mbedtls_ssl_context *ssl )
 {
diff --git a/scripts/generate_ssl_debug_helpers.py b/scripts/generate_ssl_debug_helpers.py
new file mode 100755
index 0000000..9b626fe
--- /dev/null
+++ b/scripts/generate_ssl_debug_helpers.py
@@ -0,0 +1,310 @@
+#!/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<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 preprocesse_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<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 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<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(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 preprocesse_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)
diff --git a/scripts/make_generated_files.bat b/scripts/make_generated_files.bat
index e4465d8..d3a8b36 100644
--- a/scripts/make_generated_files.bat
+++ b/scripts/make_generated_files.bat
@@ -4,6 +4,7 @@
 perl scripts\generate_errors.pl || exit /b 1

 perl scripts\generate_query_config.pl || exit /b 1

 perl scripts\generate_features.pl || exit /b 1

+python scripts\generate_ssl_debug_helpers.py || exit /b 1

 perl scripts\generate_visualc_files.pl || exit /b 1

 python scripts\generate_psa_constants.py || exit /b 1

 python tests\scripts\generate_psa_tests.py || exit /b 1

diff --git a/tests/scripts/check-generated-files.sh b/tests/scripts/check-generated-files.sh
index 0399484..994fd24 100755
--- a/tests/scripts/check-generated-files.sh
+++ b/tests/scripts/check-generated-files.sh
@@ -118,6 +118,7 @@
 check scripts/generate_errors.pl library/error.c
 check scripts/generate_query_config.pl programs/test/query_config.c
 check scripts/generate_features.pl library/version_features.c
+check scripts/generate_ssl_debug_helpers.py library/ssl_debug_helpers_generated.c
 # generate_visualc_files enumerates source files (library/*.c). It doesn't
 # care about their content, but the files must exist. So it must run after
 # the step that creates or updates these files.