• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2
3"""Generate library/ssl_debug_helps_generated.c
4
5The code generated by this module includes debug helper functions that can not be
6implemented by fixed codes.
7
8"""
9
10# Copyright The Mbed TLS Contributors
11# SPDX-License-Identifier: Apache-2.0
12#
13# Licensed under the Apache License, Version 2.0 (the "License"); you may
14# not use this file except in compliance with the License.
15# You may obtain a copy of the License at
16#
17# http://www.apache.org/licenses/LICENSE-2.0
18#
19# Unless required by applicable law or agreed to in writing, software
20# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
21# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22# See the License for the specific language governing permissions and
23# limitations under the License.
24import sys
25import re
26import os
27import textwrap
28import argparse
29from mbedtls_dev import build_tree
30
31
32def remove_c_comments(string):
33    """
34        Remove C style comments from input string
35    """
36    string_pattern = r"(?P<string>\".*?\"|\'.*?\')"
37    comment_pattern = r"(?P<comment>/\*.*?\*/|//[^\r\n]*$)"
38    pattern = re.compile(string_pattern + r'|' + comment_pattern,
39                         re.MULTILINE | re.DOTALL)
40
41    def replacer(match):
42        if match.lastgroup == 'comment':
43            return ""
44        return match.group()
45    return pattern.sub(replacer, string)
46
47
48class CondDirectiveNotMatch(Exception):
49    pass
50
51
52def preprocess_c_source_code(source, *classes):
53    """
54        Simple preprocessor for C source code.
55
56        Only processses condition directives without expanding them.
57        Yield object according to the classes input. Most match firstly
58
59        If the directive pair does not match , raise CondDirectiveNotMatch.
60
61        Assume source code does not include comments and compile pass.
62
63    """
64
65    pattern = re.compile(r"^[ \t]*#[ \t]*" +
66                         r"(?P<directive>(if[ \t]|ifndef[ \t]|ifdef[ \t]|else|endif))" +
67                         r"[ \t]*(?P<param>(.*\\\n)*.*$)",
68                         re.MULTILINE)
69    stack = []
70
71    def _yield_objects(s, d, p, st, end):
72        """
73            Output matched source piece
74        """
75        nonlocal stack
76        start_line, end_line = '', ''
77        if stack:
78            start_line = '#{} {}'.format(d, p)
79            if d == 'if':
80                end_line = '#endif /* {} */'.format(p)
81            elif d == 'ifdef':
82                end_line = '#endif /* defined({}) */'.format(p)
83            else:
84                end_line = '#endif /* !defined({}) */'.format(p)
85        has_instance = False
86        for cls in classes:
87            for instance in cls.extract(s, st, end):
88                if has_instance is False:
89                    has_instance = True
90                    yield pair_start, start_line
91                yield instance.span()[0], instance
92        if has_instance:
93            yield start, end_line
94
95    for match in pattern.finditer(source):
96
97        directive = match.groupdict()['directive'].strip()
98        param = match.groupdict()['param']
99        start, end = match.span()
100
101        if directive in ('if', 'ifndef', 'ifdef'):
102            stack.append((directive, param, start, end))
103            continue
104
105        if not stack:
106            raise CondDirectiveNotMatch()
107
108        pair_directive, pair_param, pair_start, pair_end = stack.pop()
109        yield from _yield_objects(source,
110                                  pair_directive,
111                                  pair_param,
112                                  pair_end,
113                                  start)
114
115        if directive == 'endif':
116            continue
117
118        if pair_directive == 'if':
119            directive = 'if'
120            param = "!( {} )".format(pair_param)
121        elif pair_directive == 'ifdef':
122            directive = 'ifndef'
123            param = pair_param
124        else:
125            directive = 'ifdef'
126            param = pair_param
127
128        stack.append((directive, param, start, end))
129    assert not stack, len(stack)
130
131
132class EnumDefinition:
133    """
134        Generate helper functions around enumeration.
135
136        Currently, it generate translation function from enum value to string.
137        Enum definition looks like:
138        [typedef] enum [prefix name] { [body] } [suffix name];
139
140        Known limitation:
141        - the '}' and ';' SHOULD NOT exist in different macro blocks. Like
142        ```
143        enum test {
144            ....
145        #if defined(A)
146            ....
147        };
148        #else
149            ....
150        };
151        #endif
152        ```
153    """
154
155    @classmethod
156    def extract(cls, source_code, start=0, end=-1):
157        enum_pattern = re.compile(r'enum\s*(?P<prefix_name>\w*)\s*' +
158                                  r'{\s*(?P<body>[^}]*)}' +
159                                  r'\s*(?P<suffix_name>\w*)\s*;',
160                                  re.MULTILINE | re.DOTALL)
161
162        for match in enum_pattern.finditer(source_code, start, end):
163            yield EnumDefinition(source_code,
164                                 span=match.span(),
165                                 group=match.groupdict())
166
167    def __init__(self, source_code, span=None, group=None):
168        assert isinstance(group, dict)
169        prefix_name = group.get('prefix_name', None)
170        suffix_name = group.get('suffix_name', None)
171        body = group.get('body', None)
172        assert prefix_name or suffix_name
173        assert body
174        assert span
175        # If suffix_name exists, it is a typedef
176        self._prototype = suffix_name if suffix_name else 'enum ' + prefix_name
177        self._name = suffix_name if suffix_name else prefix_name
178        self._body = body
179        self._source = source_code
180        self._span = span
181
182    def __repr__(self):
183        return 'Enum({},{})'.format(self._name, self._span)
184
185    def __str__(self):
186        return repr(self)
187
188    def span(self):
189        return self._span
190
191    def generate_tranlation_function(self):
192        """
193            Generate function for translating value to string
194        """
195        translation_table = []
196
197        for line in self._body.splitlines():
198
199            if line.strip().startswith('#'):
200                # Preprocess directive, keep it in table
201                translation_table.append(line.strip())
202                continue
203
204            if not line.strip():
205                continue
206
207            for field in line.strip().split(','):
208                if not field.strip():
209                    continue
210                member = field.strip().split()[0]
211                translation_table.append(
212                    '{space}[{member}] = "{member}",'.format(member=member,
213                                                             space=' '*8)
214                )
215
216        body = textwrap.dedent('''\
217            const char *{name}_str( {prototype} in )
218            {{
219                const char * in_to_str[]=
220                {{
221            {translation_table}
222                }};
223
224                if( in > ( sizeof( in_to_str )/sizeof( in_to_str[0]) - 1 ) ||
225                    in_to_str[ in ] == NULL )
226                {{
227                    return "UNKOWN_VAULE";
228                }}
229                return in_to_str[ in ];
230            }}
231                    ''')
232        body = body.format(translation_table='\n'.join(translation_table),
233                           name=self._name,
234                           prototype=self._prototype)
235        prototype = 'const char *{name}_str( {prototype} in );\n'
236        prototype = prototype.format(name=self._name,
237                                     prototype=self._prototype)
238        return body, prototype
239
240
241OUTPUT_C_TEMPLATE = '''\
242/* Automatically generated by generate_ssl_debug_helpers.py. DO NOT EDIT. */
243
244#include "common.h"
245
246#if defined(MBEDTLS_DEBUG_C)
247
248#include "ssl_debug_helpers_generated.h"
249
250{functions}
251
252#endif /* MBEDTLS_DEBUG_C */
253/* End of automatically generated file. */
254
255'''
256
257OUTPUT_H_TEMPLATE = '''\
258/* Automatically generated by generate_ssl_debug_helpers.py. DO NOT EDIT. */
259#ifndef MBEDTLS_SSL_DEBUG_HELPERS_H
260#define MBEDTLS_SSL_DEBUG_HELPERS_H
261
262#include "common.h"
263
264#if defined(MBEDTLS_DEBUG_C)
265
266#include "mbedtls/ssl.h"
267#include "ssl_misc.h"
268
269{functions}
270
271#endif /* MBEDTLS_DEBUG_C */
272
273#endif /* SSL_DEBUG_HELPERS_H */
274
275/* End of automatically generated file. */
276
277'''
278
279
280def generate_ssl_debug_helpers(output_directory, mbedtls_root):
281    """
282        Generate functions of debug helps
283    """
284    mbedtls_root = os.path.abspath(mbedtls_root or build_tree.guess_mbedtls_root())
285    with open(os.path.join(mbedtls_root, 'include/mbedtls/ssl.h')) as f:
286        source_code = remove_c_comments(f.read())
287
288    definitions = dict()
289    prototypes = dict()
290    for start, instance in preprocess_c_source_code(source_code, EnumDefinition):
291        if start in definitions:
292            continue
293        if isinstance(instance, EnumDefinition):
294            definition, prototype = instance.generate_tranlation_function()
295        else:
296            definition = instance
297            prototype = instance
298        definitions[start] = definition
299        prototypes[start] = prototype
300
301    function_definitions = [str(v) for _, v in sorted(definitions.items())]
302    function_prototypes = [str(v) for _, v in sorted(prototypes.items())]
303    if output_directory == sys.stdout:
304        sys.stdout.write(OUTPUT_H_TEMPLATE.format(
305            functions='\n'.join(function_prototypes)))
306        sys.stdout.write(OUTPUT_C_TEMPLATE.format(
307            functions='\n'.join(function_definitions)))
308    else:
309        with open(os.path.join(output_directory, 'ssl_debug_helpers_generated.c'), 'w') as f:
310            f.write(OUTPUT_C_TEMPLATE.format(
311                functions='\n'.join(function_definitions)))
312
313        with open(os.path.join(output_directory, 'ssl_debug_helpers_generated.h'), 'w') as f:
314            f.write(OUTPUT_H_TEMPLATE.format(
315                functions='\n'.join(function_prototypes)))
316
317
318def main():
319    """
320    Command line entry
321    """
322    parser = argparse.ArgumentParser()
323    parser.add_argument('--mbedtls-root', nargs='?', default=None,
324                        help='root directory of mbedtls source code')
325    parser.add_argument('output_directory', nargs='?',
326                        default='library', help='source/header files location')
327
328    args = parser.parse_args()
329
330    generate_ssl_debug_helpers(args.output_directory, args.mbedtls_root)
331    return 0
332
333
334if __name__ == '__main__':
335    sys.exit(main())
336