blob: c3b1b7a3f8ab884010ee53dc13ec32950693b6b4 [file] [log] [blame]
Azim Khan951a2c82018-06-29 03:47:08 +01001# Greentea host test script for Mbed TLS on-target test suite testing.
Azim Khanf0e42fb2017-08-02 14:47:13 +01002#
Mohammad Azim Khan78befd92018-03-06 11:49:41 +00003# Copyright (C) 2018, ARM Limited, All Rights Reserved
Azim Khanf0e42fb2017-08-02 14:47:13 +01004# SPDX-License-Identifier: Apache-2.0
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17#
18# This file is part of mbed TLS (https://tls.mbed.org)
19
20
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010021"""
Azim Khan951a2c82018-06-29 03:47:08 +010022Mbed TLS on-target test suite tests are implemented as mbed-os greentea
23tests. Greentea tests are implemented in two parts: target test and
24host test. Target test is a C application that is built for the
25target platform and executes on the target. Host test is a Python
26class derived from mbed_host_tests.BaseHostTest. Target communicates
27with the host over serial for the test data.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010028
Azim Khan951a2c82018-06-29 03:47:08 +010029Python tool mbedgt (greentea) is responsible for flashing the test
30binary on to the target and dynamically loading the host test.
31
32This script contains the host test for handling target test's
33requests for test vectors. It also reports the test results
34in format understood by Greentea.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010035"""
36
Azim Khanf0e42fb2017-08-02 14:47:13 +010037
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010038import re
39import os
Azim Khan663d4702017-07-07 15:40:26 +010040import binascii
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010041from mbed_host_tests import BaseHostTest, event_callback
42
43
Azim Khanb98e6ee2018-06-28 17:11:33 +010044class TestDataParserError(Exception):
45 """Indicates error in test data, read from .data file."""
46 pass
47
48
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010049class TestDataParser(object):
50 """
Azim Khan951a2c82018-06-29 03:47:08 +010051 Parses test name, dependencies, test function name and test parameters
52 from the data file.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010053 """
54
55 def __init__(self):
56 """
57 Constructor
58 """
59 self.tests = []
60
61 def parse(self, data_file):
62 """
Azim Khanf0e42fb2017-08-02 14:47:13 +010063 Data file parser.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010064
Azim Khanf0e42fb2017-08-02 14:47:13 +010065 :param data_file: Data file path
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010066 """
67 with open(data_file, 'r') as f:
68 self.__parse(f)
69
70 @staticmethod
71 def __escaped_split(str, ch):
72 """
Azim Khanf0e42fb2017-08-02 14:47:13 +010073 Splits str on ch except when escaped.
74
75 :param str: String to split
76 :param ch: Split character
77 :return: List of splits
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +010078 """
79 if len(ch) > 1:
80 raise ValueError('Expected split character. Found string!')
81 out = []
82 part = ''
83 escape = False
84 for i in range(len(str)):
85 if not escape and str[i] == ch:
86 out.append(part)
87 part = ''
88 else:
89 part += str[i]
90 escape = not escape and str[i] == '\\'
91 if len(part):
92 out.append(part)
93 return out
94
95 def __parse(self, file):
96 """
Azim Khanf0e42fb2017-08-02 14:47:13 +010097 Parses data file using supplied file object.
98
99 :param file: Data file object
100 :return:
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100101 """
Azim Khan663d4702017-07-07 15:40:26 +0100102 for line in file:
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100103 line = line.strip()
104 if len(line) == 0:
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100105 continue
106 # Read test name
107 name = line
108
109 # Check dependencies
110 deps = []
Azim Khan663d4702017-07-07 15:40:26 +0100111 line = file.next().strip()
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100112 m = re.search('depends_on\:(.*)', line)
113 if m:
114 deps = [int(x) for x in m.group(1).split(':')]
Azim Khan663d4702017-07-07 15:40:26 +0100115 line = file.next().strip()
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100116
117 # Read test vectors
Azim Khan663d4702017-07-07 15:40:26 +0100118 line = line.replace('\\n', '\n')
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100119 parts = self.__escaped_split(line, ':')
120 function = int(parts[0])
121 x = parts[1:]
122 l = len(x)
Azim Khanb98e6ee2018-06-28 17:11:33 +0100123 if l % 2 != 0:
124 raise TestDataParserError("Number of test arguments should "
125 "be even: %s" % line)
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100126 args = [(x[i * 2], x[(i * 2) + 1]) for i in range(len(x)/2)]
127 self.tests.append((name, function, deps, args))
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100128
129 def get_test_data(self):
130 """
Azim Khanf0e42fb2017-08-02 14:47:13 +0100131 Returns test data.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100132 """
133 return self.tests
134
135
136class MbedTlsTest(BaseHostTest):
137 """
Azim Khan951a2c82018-06-29 03:47:08 +0100138 Host test for mbedtls unit tests. This script is loaded at
139 run time by Greentea for executing mbedtls test suites. Each
140 communication from the target is received in this object as
141 an event, which is then handled by the event handler method
142 decorated by the associated event. Ex: @event_callback('GO').
143
144 Target test sends requests for dispatching next test. It reads
145 tests from the intermediate data file and sends test function
146 identifier, dependency identifiers, expression identifiers and
147 the test data in binary form. Target test checks dependecnies
148 , evaluate integer constant expressions and dispatches the test
149 function with received test parameters.
150
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100151 """
Azim Khan951a2c82018-06-29 03:47:08 +0100152 # status/error codes from suites/helpers.function
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100153 DEPENDENCY_SUPPORTED = 0
154 KEY_VALUE_MAPPING_FOUND = DEPENDENCY_SUPPORTED
155 DISPATCH_TEST_SUCCESS = DEPENDENCY_SUPPORTED
156
Azim Khan951a2c82018-06-29 03:47:08 +0100157 KEY_VALUE_MAPPING_NOT_FOUND = -1 # Expression Id not found.
158 DEPENDENCY_NOT_SUPPORTED = -2 # Dependency not supported.
159 DISPATCH_TEST_FN_NOT_FOUND = -3 # Test function not found.
160 DISPATCH_INVALID_TEST_DATA = -4 # Invalid parameter type.
161 DISPATCH_UNSUPPORTED_SUITE = -5 # Test suite not supported/enabled.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100162
163 def __init__(self):
164 """
Azim Khanf0e42fb2017-08-02 14:47:13 +0100165 Constructor initialises test index to 0.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100166 """
167 super(MbedTlsTest, self).__init__()
168 self.tests = []
169 self.test_index = -1
170 self.dep_index = 0
171 self.error_str = dict()
172 self.error_str[self.DEPENDENCY_SUPPORTED] = 'DEPENDENCY_SUPPORTED'
173 self.error_str[self.KEY_VALUE_MAPPING_NOT_FOUND] = 'KEY_VALUE_MAPPING_NOT_FOUND'
174 self.error_str[self.DEPENDENCY_NOT_SUPPORTED] = 'DEPENDENCY_NOT_SUPPORTED'
175 self.error_str[self.DISPATCH_TEST_FN_NOT_FOUND] = 'DISPATCH_TEST_FN_NOT_FOUND'
176 self.error_str[self.DISPATCH_INVALID_TEST_DATA] = 'DISPATCH_INVALID_TEST_DATA'
177 self.error_str[self.DISPATCH_UNSUPPORTED_SUITE] = 'DISPATCH_UNSUPPORTED_SUITE'
178
179 def setup(self):
180 """
Azim Khan951a2c82018-06-29 03:47:08 +0100181 Setup hook implementation. Reads test suite data file and parses out
182 tests.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100183 """
184 binary_path = self.get_config_item('image_path')
185 script_dir = os.path.split(os.path.abspath(__file__))[0]
186 suite_name = os.path.splitext(os.path.basename(binary_path))[0]
187 data_file = ".".join((suite_name, 'data'))
Azim Khan951a2c82018-06-29 03:47:08 +0100188 data_file = os.path.join(script_dir, '..', 'mbedtls',
189 suite_name, data_file)
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100190 if os.path.exists(data_file):
191 self.log("Running tests from %s" % data_file)
192 parser = TestDataParser()
193 parser.parse(data_file)
194 self.tests = parser.get_test_data()
195 self.print_test_info()
196 else:
197 self.log("Data file not found: %s" % data_file)
198 self.notify_complete(False)
199
200 def print_test_info(self):
201 """
Azim Khanf0e42fb2017-08-02 14:47:13 +0100202 Prints test summary read by Greentea to detect test cases.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100203 """
204 self.log('{{__testcase_count;%d}}' % len(self.tests))
205 for name, _, _, _ in self.tests:
206 self.log('{{__testcase_name;%s}}' % name)
207
208 @staticmethod
209 def align_32bit(b):
210 """
Azim Khanf0e42fb2017-08-02 14:47:13 +0100211 4 byte aligns input byte array.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100212
213 :return:
214 """
215 b += bytearray((4 - (len(b))) % 4)
216
Azim Khand59391a2017-06-01 14:04:17 +0100217 @staticmethod
218 def hex_str_bytes(hex_str):
219 """
220 Converts Hex string representation to byte array
221
Azim Khanf0e42fb2017-08-02 14:47:13 +0100222 :param hex_str: Hex in string format.
223 :return: Output Byte array
Azim Khand59391a2017-06-01 14:04:17 +0100224 """
Azim Khanb98e6ee2018-06-28 17:11:33 +0100225 if hex_str[0] != '"' or hex_str[len(hex_str) - 1] != '"':
226 raise TestDataParserError("HEX test parameter missing '\"':"
227 " %s" % hex_str)
Azim Khand59391a2017-06-01 14:04:17 +0100228 hex_str = hex_str.strip('"')
Azim Khanb98e6ee2018-06-28 17:11:33 +0100229 if len(hex_str) % 2 != 0:
230 raise TestDataParserError("HEX parameter len should be mod of "
231 "2: %s" % hex_str)
Azim Khand59391a2017-06-01 14:04:17 +0100232
Azim Khan663d4702017-07-07 15:40:26 +0100233 b = binascii.unhexlify(hex_str)
Azim Khand59391a2017-06-01 14:04:17 +0100234 return b
235
Azim Khan663d4702017-07-07 15:40:26 +0100236 @staticmethod
237 def int32_to_bigendian_bytes(i):
238 """
239 Coverts i to bytearray in big endian format.
240
Azim Khanf0e42fb2017-08-02 14:47:13 +0100241 :param i: Input integer
242 :return: Output bytes array in big endian or network order
Azim Khan663d4702017-07-07 15:40:26 +0100243 """
244 b = bytearray([((i >> x) & 0xff) for x in [24, 16, 8, 0]])
245 return b
246
247 def test_vector_to_bytes(self, function_id, deps, parameters):
248 """
249 Converts test vector into a byte array that can be sent to the target.
250
Azim Khanf0e42fb2017-08-02 14:47:13 +0100251 :param function_id: Test Function Identifier
252 :param deps: Dependency list
253 :param parameters: Test function input parameters
254 :return: Byte array and its length
Azim Khan663d4702017-07-07 15:40:26 +0100255 """
256 b = bytearray([len(deps)])
257 if len(deps):
258 b += bytearray(deps)
259 b += bytearray([function_id, len(parameters)])
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100260 for typ, param in parameters:
261 if typ == 'int' or typ == 'exp':
262 i = int(param)
263 b += 'I' if typ == 'int' else 'E'
264 self.align_32bit(b)
Azim Khan663d4702017-07-07 15:40:26 +0100265 b += self.int32_to_bigendian_bytes(i)
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100266 elif typ == 'char*':
267 param = param.strip('"')
268 i = len(param) + 1 # + 1 for null termination
269 b += 'S'
270 self.align_32bit(b)
Azim Khan663d4702017-07-07 15:40:26 +0100271 b += self.int32_to_bigendian_bytes(i)
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100272 b += bytearray(list(param))
273 b += '\0' # Null terminate
Azim Khand59391a2017-06-01 14:04:17 +0100274 elif typ == 'hex':
275 hb = self.hex_str_bytes(param)
276 b += 'H'
277 self.align_32bit(b)
278 i = len(hb)
Azim Khan663d4702017-07-07 15:40:26 +0100279 b += self.int32_to_bigendian_bytes(i)
Azim Khand59391a2017-06-01 14:04:17 +0100280 b += hb
Azim Khan663d4702017-07-07 15:40:26 +0100281 length = self.int32_to_bigendian_bytes(len(b))
282 return b, length
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100283
284 def run_next_test(self):
285 """
Azim Khan951a2c82018-06-29 03:47:08 +0100286 Fetch next test information and execute the test.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100287
288 """
289 self.test_index += 1
290 self.dep_index = 0
291 if self.test_index < len(self.tests):
Azim Khan663d4702017-07-07 15:40:26 +0100292 name, function_id, deps, args = self.tests[self.test_index]
293 self.run_test(name, function_id, deps, args)
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100294 else:
295 self.notify_complete(True)
296
Azim Khan663d4702017-07-07 15:40:26 +0100297 def run_test(self, name, function_id, deps, args):
298 """
Azim Khan951a2c82018-06-29 03:47:08 +0100299 Execute the test on target by sending next test information.
Azim Khan663d4702017-07-07 15:40:26 +0100300
Azim Khanf0e42fb2017-08-02 14:47:13 +0100301 :param name: Test name
302 :param function_id: function identifier
303 :param deps: Dependencies list
304 :param args: test parameters
Azim Khan663d4702017-07-07 15:40:26 +0100305 :return:
306 """
307 self.log("Running: %s" % name)
308
309 bytes, length = self.test_vector_to_bytes(function_id, deps, args)
310 self.send_kv(length, bytes)
311
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100312 @staticmethod
313 def get_result(value):
Azim Khanf0e42fb2017-08-02 14:47:13 +0100314 """
315 Converts result from string type to integer
316 :param value: Result code in string
317 :return: Integer result code
318 """
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100319 try:
320 return int(value)
321 except ValueError:
322 ValueError("Result should return error number. Instead received %s" % value)
323 return 0
324
325 @event_callback('GO')
326 def on_go(self, key, value, timestamp):
Azim Khanf0e42fb2017-08-02 14:47:13 +0100327 """
Azim Khan951a2c82018-06-29 03:47:08 +0100328 Sent by the target to start first test.
Azim Khanf0e42fb2017-08-02 14:47:13 +0100329
330 :param key: Event key
331 :param value: Value. ignored
332 :param timestamp: Timestamp ignored.
333 :return:
334 """
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100335 self.run_next_test()
336
337 @event_callback("R")
338 def on_result(self, key, value, timestamp):
339 """
Azim Khan951a2c82018-06-29 03:47:08 +0100340 Handle result. Prints test start, finish required by Greentea
341 to detect test execution.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100342
Azim Khanf0e42fb2017-08-02 14:47:13 +0100343 :param key: Event key
344 :param value: Value. ignored
345 :param timestamp: Timestamp ignored.
346 :return:
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100347 """
348 int_val = self.get_result(value)
349 name, function, deps, args = self.tests[self.test_index]
Azim Khan5e7f8df2017-05-31 20:33:39 +0100350 self.log('{{__testcase_start;%s}}' % name)
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100351 self.log('{{__testcase_finish;%s;%d;%d}}' % (name, int_val == 0,
352 int_val != 0))
353 self.run_next_test()
354
355 @event_callback("F")
356 def on_failure(self, key, value, timestamp):
357 """
Azim Khanf0e42fb2017-08-02 14:47:13 +0100358 Handles test execution failure. That means dependency not supported or
359 Test function not supported. Hence marking test as skipped.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100360
Azim Khanf0e42fb2017-08-02 14:47:13 +0100361 :param key: Event key
362 :param value: Value. ignored
363 :param timestamp: Timestamp ignored.
Mohammad Azim Khan7a0d84f2017-04-01 03:18:20 +0100364 :return:
365 """
366 int_val = self.get_result(value)
367 name, function, deps, args = self.tests[self.test_index]
368 if int_val in self.error_str:
369 err = self.error_str[int_val]
370 else:
371 err = 'Unknown error'
372 # For skip status, do not write {{__testcase_finish;...}}
373 self.log("Error: %s" % err)
374 self.run_next_test()