generate_ssl_debug_helpers.py 14 KB

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