tls_test_case.py (3940B)
1 """Library for constructing an Mbed TLS ssl-opt test case. 2 """ 3 4 # Copyright The Mbed TLS Contributors 5 # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later 6 7 import enum 8 import re 9 from typing import List 10 11 from . import typing_util 12 13 14 class TestCase: 15 """Data about an ssl-opt test case.""" 16 #pylint: disable=too-few-public-methods 17 18 def __init__(self, description: str) -> None: 19 # List of shell snippets to call before run_test, typically 20 # calls to requires_xxx functions. 21 self.requirements = [] #type: List[str] 22 # Test case description (first argument to run_test). 23 self.description = description 24 # Client command line. 25 # This will be placed directly inside double quotes in the shell script. 26 self.client = '$P_CLI' 27 # Server command line. 28 # This will be placed directly inside double quotes in the shell script. 29 self.server = '$P_SRV' 30 # Expected client exit code. 31 self.exit_code = 0 32 33 # Note that all patterns matched in the logs are in BRE 34 # (Basic Regular Expression) syntax, more precisely in the BRE 35 # dialect that is the default for GNU grep. The main difference 36 # with Python regular expressions is that the operators for 37 # grouping `\(...\)`, alternation `x\|y`, option `x\?`, 38 # one-or-more `x\+` and repetition ranges `x\{M,N\}` must be 39 # preceded by a backslash. The characters `()|?+{}` stand for 40 # themselves. 41 42 # BRE for text that must be present in the client log (run_test -c). 43 self.wanted_client_patterns = [] #type: List[str] 44 # BRE for text that must be present in the server log (run_test -s). 45 self.wanted_server_patterns = [] #type: List[str] 46 # BRE for text that must not be present in the client log (run_test -C). 47 self.forbidden_client_patterns = [] #type: List[str] 48 # BRE for text that must not be present in the server log (run_test -S). 49 self.forbidden_server_patterns = [] #type: List[str] 50 51 @staticmethod 52 def _quote(raw: str) -> str: 53 """Quote the given string for sh. 54 55 Use double quotes, because that's currently the norm in ssl-opt.sh. 56 """ 57 return '"' + re.sub(r'([$"\\`])', r'\\\1', raw) + '"' 58 59 def write(self, out: typing_util.Writable) -> None: 60 """Write the test case to the specified file.""" 61 for req in self.requirements: 62 out.write(req + '\n') 63 out.write(f'run_test {self._quote(self.description)} \\\n') 64 out.write(f' "{self.server}" \\\n') 65 out.write(f' "{self.client}" \\\n') 66 out.write(f' {self.exit_code}') 67 for pat in self.wanted_server_patterns: 68 out.write(' \\\n -s ' + self._quote(pat)) 69 for pat in self.forbidden_server_patterns: 70 out.write(' \\\n -S ' + self._quote(pat)) 71 for pat in self.wanted_client_patterns: 72 out.write(' \\\n -c ' + self._quote(pat)) 73 for pat in self.forbidden_client_patterns: 74 out.write(' \\\n -C ' + self._quote(pat)) 75 out.write('\n\n') 76 77 78 class Side(enum.Enum): 79 CLIENT = 0 80 SERVER = 1 81 82 class Version(enum.Enum): 83 """TLS protocol version. 84 85 This class doesn't know about DTLS yet. 86 """ 87 88 TLS12 = 2 89 TLS13 = 3 90 91 def force_version(self) -> str: 92 """Argument to pass to ssl_client2 or ssl_server2 to force this version.""" 93 return f'force_version=tls1{self.value}' 94 95 def openssl_option(self) -> str: 96 """Option to pass to openssl s_client or openssl s_server to select this version.""" 97 return f'-tls1_{self.value}' 98 99 def requires_command(self) -> str: 100 """Command to require this protocol version in an ssl-opt.sh test case.""" 101 return 'requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_' + str(self.value)