mbedtls_test.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. #!/usr/bin/env python3
  2. # Greentea host test script for Mbed TLS on-target test suite testing.
  3. #
  4. # Copyright The Mbed TLS Contributors
  5. # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
  6. #
  7. # This file is provided under the Apache License 2.0, or the
  8. # GNU General Public License v2.0 or later.
  9. #
  10. # **********
  11. # Apache License 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.
  24. #
  25. # **********
  26. #
  27. # **********
  28. # GNU General Public License v2.0 or later:
  29. #
  30. # This program is free software; you can redistribute it and/or modify
  31. # it under the terms of the GNU General Public License as published by
  32. # the Free Software Foundation; either version 2 of the License, or
  33. # (at your option) any later version.
  34. #
  35. # This program is distributed in the hope that it will be useful,
  36. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  37. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  38. # GNU General Public License for more details.
  39. #
  40. # You should have received a copy of the GNU General Public License along
  41. # with this program; if not, write to the Free Software Foundation, Inc.,
  42. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  43. #
  44. # **********
  45. """
  46. Mbed TLS on-target test suite tests are implemented as Greentea
  47. tests. Greentea tests are implemented in two parts: target test and
  48. host test. Target test is a C application that is built for the
  49. target platform and executes on the target. Host test is a Python
  50. class derived from mbed_host_tests.BaseHostTest. Target communicates
  51. with the host over serial for the test data and sends back the result.
  52. Python tool mbedgt (Greentea) is responsible for flashing the test
  53. binary on to the target and dynamically loading this host test module.
  54. Greentea documentation can be found here:
  55. https://github.com/ARMmbed/greentea
  56. """
  57. import re
  58. import os
  59. import binascii
  60. from mbed_host_tests import BaseHostTest, event_callback # pylint: disable=import-error
  61. class TestDataParserError(Exception):
  62. """Indicates error in test data, read from .data file."""
  63. pass
  64. class TestDataParser:
  65. """
  66. Parses test name, dependencies, test function name and test parameters
  67. from the data file.
  68. """
  69. def __init__(self):
  70. """
  71. Constructor
  72. """
  73. self.tests = []
  74. def parse(self, data_file):
  75. """
  76. Data file parser.
  77. :param data_file: Data file path
  78. """
  79. with open(data_file, 'r') as data_f:
  80. self.__parse(data_f)
  81. @staticmethod
  82. def __escaped_split(inp_str, split_char):
  83. """
  84. Splits inp_str on split_char except when escaped.
  85. :param inp_str: String to split
  86. :param split_char: Split character
  87. :return: List of splits
  88. """
  89. split_colon_fn = lambda x: re.sub(r'\\' + split_char, split_char, x)
  90. if len(split_char) > 1:
  91. raise ValueError('Expected split character. Found string!')
  92. out = list(map(split_colon_fn, re.split(r'(?<!\\)' + split_char, inp_str)))
  93. out = [x for x in out if x]
  94. return out
  95. def __parse(self, data_f):
  96. """
  97. Parses data file using supplied file object.
  98. :param data_f: Data file object
  99. :return:
  100. """
  101. for line in data_f:
  102. line = line.strip()
  103. if not line:
  104. continue
  105. # Read test name
  106. name = line
  107. # Check dependencies
  108. dependencies = []
  109. line = next(data_f).strip()
  110. match = re.search('depends_on:(.*)', line)
  111. if match:
  112. dependencies = [int(x) for x in match.group(1).split(':')]
  113. line = next(data_f).strip()
  114. # Read test vectors
  115. line = line.replace('\\n', '\n')
  116. parts = self.__escaped_split(line, ':')
  117. function_name = int(parts[0])
  118. args = parts[1:]
  119. args_count = len(args)
  120. if args_count % 2 != 0:
  121. err_str_fmt = "Number of test arguments({}) should be even: {}"
  122. raise TestDataParserError(err_str_fmt.format(args_count, line))
  123. grouped_args = [(args[i * 2], args[(i * 2) + 1])
  124. for i in range(int(len(args)/2))]
  125. self.tests.append((name, function_name, dependencies,
  126. grouped_args))
  127. def get_test_data(self):
  128. """
  129. Returns test data.
  130. """
  131. return self.tests
  132. class MbedTlsTest(BaseHostTest):
  133. """
  134. Host test for Mbed TLS unit tests. This script is loaded at
  135. run time by Greentea for executing Mbed TLS test suites. Each
  136. communication from the target is received in this object as
  137. an event, which is then handled by the event handler method
  138. decorated by the associated event. Ex: @event_callback('GO').
  139. Target test sends requests for dispatching next test. It reads
  140. tests from the intermediate data file and sends test function
  141. identifier, dependency identifiers, expression identifiers and
  142. the test data in binary form. Target test checks dependencies
  143. , evaluate integer constant expressions and dispatches the test
  144. function with received test parameters. After test function is
  145. finished, target sends the result. This class handles the result
  146. event and prints verdict in the form that Greentea understands.
  147. """
  148. # status/error codes from suites/helpers.function
  149. DEPENDENCY_SUPPORTED = 0
  150. KEY_VALUE_MAPPING_FOUND = DEPENDENCY_SUPPORTED
  151. DISPATCH_TEST_SUCCESS = DEPENDENCY_SUPPORTED
  152. KEY_VALUE_MAPPING_NOT_FOUND = -1 # Expression Id not found.
  153. DEPENDENCY_NOT_SUPPORTED = -2 # Dependency not supported.
  154. DISPATCH_TEST_FN_NOT_FOUND = -3 # Test function not found.
  155. DISPATCH_INVALID_TEST_DATA = -4 # Invalid parameter type.
  156. DISPATCH_UNSUPPORTED_SUITE = -5 # Test suite not supported/enabled.
  157. def __init__(self):
  158. """
  159. Constructor initialises test index to 0.
  160. """
  161. super(MbedTlsTest, self).__init__()
  162. self.tests = []
  163. self.test_index = -1
  164. self.dep_index = 0
  165. self.suite_passed = True
  166. self.error_str = dict()
  167. self.error_str[self.DEPENDENCY_SUPPORTED] = \
  168. 'DEPENDENCY_SUPPORTED'
  169. self.error_str[self.KEY_VALUE_MAPPING_NOT_FOUND] = \
  170. 'KEY_VALUE_MAPPING_NOT_FOUND'
  171. self.error_str[self.DEPENDENCY_NOT_SUPPORTED] = \
  172. 'DEPENDENCY_NOT_SUPPORTED'
  173. self.error_str[self.DISPATCH_TEST_FN_NOT_FOUND] = \
  174. 'DISPATCH_TEST_FN_NOT_FOUND'
  175. self.error_str[self.DISPATCH_INVALID_TEST_DATA] = \
  176. 'DISPATCH_INVALID_TEST_DATA'
  177. self.error_str[self.DISPATCH_UNSUPPORTED_SUITE] = \
  178. 'DISPATCH_UNSUPPORTED_SUITE'
  179. def setup(self):
  180. """
  181. Setup hook implementation. Reads test suite data file and parses out
  182. tests.
  183. """
  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, 'datax'))
  188. data_file = os.path.join(script_dir, '..', 'mbedtls',
  189. suite_name, data_file)
  190. 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. def print_test_info(self):
  200. """
  201. Prints test summary read by Greentea to detect test cases.
  202. """
  203. self.log('{{__testcase_count;%d}}' % len(self.tests))
  204. for name, _, _, _ in self.tests:
  205. self.log('{{__testcase_name;%s}}' % name)
  206. @staticmethod
  207. def align_32bit(data_bytes):
  208. """
  209. 4 byte aligns input byte array.
  210. :return:
  211. """
  212. data_bytes += bytearray((4 - (len(data_bytes))) % 4)
  213. @staticmethod
  214. def hex_str_bytes(hex_str):
  215. """
  216. Converts Hex string representation to byte array
  217. :param hex_str: Hex in string format.
  218. :return: Output Byte array
  219. """
  220. if hex_str[0] != '"' or hex_str[len(hex_str) - 1] != '"':
  221. raise TestDataParserError("HEX test parameter missing '\"':"
  222. " %s" % hex_str)
  223. hex_str = hex_str.strip('"')
  224. if len(hex_str) % 2 != 0:
  225. raise TestDataParserError("HEX parameter len should be mod of "
  226. "2: %s" % hex_str)
  227. data_bytes = binascii.unhexlify(hex_str)
  228. return data_bytes
  229. @staticmethod
  230. def int32_to_big_endian_bytes(i):
  231. """
  232. Coverts i to byte array in big endian format.
  233. :param i: Input integer
  234. :return: Output bytes array in big endian or network order
  235. """
  236. data_bytes = bytearray([((i >> x) & 0xff) for x in [24, 16, 8, 0]])
  237. return data_bytes
  238. def test_vector_to_bytes(self, function_id, dependencies, parameters):
  239. """
  240. Converts test vector into a byte array that can be sent to the target.
  241. :param function_id: Test Function Identifier
  242. :param dependencies: Dependency list
  243. :param parameters: Test function input parameters
  244. :return: Byte array and its length
  245. """
  246. data_bytes = bytearray([len(dependencies)])
  247. if dependencies:
  248. data_bytes += bytearray(dependencies)
  249. data_bytes += bytearray([function_id, len(parameters)])
  250. for typ, param in parameters:
  251. if typ in ('int', 'exp'):
  252. i = int(param, 0)
  253. data_bytes += b'I' if typ == 'int' else b'E'
  254. self.align_32bit(data_bytes)
  255. data_bytes += self.int32_to_big_endian_bytes(i)
  256. elif typ == 'char*':
  257. param = param.strip('"')
  258. i = len(param) + 1 # + 1 for null termination
  259. data_bytes += b'S'
  260. self.align_32bit(data_bytes)
  261. data_bytes += self.int32_to_big_endian_bytes(i)
  262. data_bytes += bytearray(param, encoding='ascii')
  263. data_bytes += b'\0' # Null terminate
  264. elif typ == 'hex':
  265. binary_data = self.hex_str_bytes(param)
  266. data_bytes += b'H'
  267. self.align_32bit(data_bytes)
  268. i = len(binary_data)
  269. data_bytes += self.int32_to_big_endian_bytes(i)
  270. data_bytes += binary_data
  271. length = self.int32_to_big_endian_bytes(len(data_bytes))
  272. return data_bytes, length
  273. def run_next_test(self):
  274. """
  275. Fetch next test information and execute the test.
  276. """
  277. self.test_index += 1
  278. self.dep_index = 0
  279. if self.test_index < len(self.tests):
  280. name, function_id, dependencies, args = self.tests[self.test_index]
  281. self.run_test(name, function_id, dependencies, args)
  282. else:
  283. self.notify_complete(self.suite_passed)
  284. def run_test(self, name, function_id, dependencies, args):
  285. """
  286. Execute the test on target by sending next test information.
  287. :param name: Test name
  288. :param function_id: function identifier
  289. :param dependencies: Dependencies list
  290. :param args: test parameters
  291. :return:
  292. """
  293. self.log("Running: %s" % name)
  294. param_bytes, length = self.test_vector_to_bytes(function_id,
  295. dependencies, args)
  296. self.send_kv(
  297. ''.join('{:02x}'.format(x) for x in length),
  298. ''.join('{:02x}'.format(x) for x in param_bytes)
  299. )
  300. @staticmethod
  301. def get_result(value):
  302. """
  303. Converts result from string type to integer
  304. :param value: Result code in string
  305. :return: Integer result code. Value is from the test status
  306. constants defined under the MbedTlsTest class.
  307. """
  308. try:
  309. return int(value)
  310. except ValueError:
  311. ValueError("Result should return error number. "
  312. "Instead received %s" % value)
  313. @event_callback('GO')
  314. def on_go(self, _key, _value, _timestamp):
  315. """
  316. Sent by the target to start first test.
  317. :param _key: Event key
  318. :param _value: Value. ignored
  319. :param _timestamp: Timestamp ignored.
  320. :return:
  321. """
  322. self.run_next_test()
  323. @event_callback("R")
  324. def on_result(self, _key, value, _timestamp):
  325. """
  326. Handle result. Prints test start, finish required by Greentea
  327. to detect test execution.
  328. :param _key: Event key
  329. :param value: Value. ignored
  330. :param _timestamp: Timestamp ignored.
  331. :return:
  332. """
  333. int_val = self.get_result(value)
  334. name, _, _, _ = self.tests[self.test_index]
  335. self.log('{{__testcase_start;%s}}' % name)
  336. self.log('{{__testcase_finish;%s;%d;%d}}' % (name, int_val == 0,
  337. int_val != 0))
  338. if int_val != 0:
  339. self.suite_passed = False
  340. self.run_next_test()
  341. @event_callback("F")
  342. def on_failure(self, _key, value, _timestamp):
  343. """
  344. Handles test execution failure. That means dependency not supported or
  345. Test function not supported. Hence marking test as skipped.
  346. :param _key: Event key
  347. :param value: Value. ignored
  348. :param _timestamp: Timestamp ignored.
  349. :return:
  350. """
  351. int_val = self.get_result(value)
  352. if int_val in self.error_str:
  353. err = self.error_str[int_val]
  354. else:
  355. err = 'Unknown error'
  356. # For skip status, do not write {{__testcase_finish;...}}
  357. self.log("Error: %s" % err)
  358. self.run_next_test()