blob: 6dfb46b2fd25ea581f9c180d45fa6bc198f60512 [file] [log] [blame]
Werner Lewis545911f2022-07-08 13:54:57 +01001#!/usr/bin/env python3
2"""Generate test data for bignum functions.
3
4With no arguments, generate all test data. With non-option arguments,
5generate only the specified files.
Werner Lewis008d90d2022-08-23 16:07:37 +01006
7Class structure:
8
Gilles Peskine69feebd2022-09-16 21:41:47 +02009Child classes of test_data_generation.BaseTarget (file targets) represent an output
Werner Lewisb03420f2022-08-25 12:29:46 +010010file. These indicate where test cases will be written to, for all subclasses of
Werner Lewis64334d92022-09-14 16:26:54 +010011this target. Multiple file targets should not reuse a `target_basename`.
Werner Lewis008d90d2022-08-23 16:07:37 +010012
Werner Lewis64334d92022-09-14 16:26:54 +010013Each subclass derived from a file target can either be:
Werner Lewis008d90d2022-08-23 16:07:37 +010014 - A concrete class, representing a test function, which generates test cases.
15 - An abstract class containing shared methods and attributes, not associated
Werner Lewisb03420f2022-08-25 12:29:46 +010016 with a test function. An example is BignumOperation, which provides
17 common features used for bignum binary operations.
18
19Both concrete and abstract subclasses can be derived from, to implement
20additional test cases (see BignumCmp and BignumCmpAbs for examples of deriving
21from abstract and concrete classes).
Werner Lewis008d90d2022-08-23 16:07:37 +010022
23
Werner Lewisb03420f2022-08-25 12:29:46 +010024Adding test case generation for a function:
Werner Lewis008d90d2022-08-23 16:07:37 +010025
26A subclass representing the test function should be added, deriving from a
Werner Lewis64334d92022-09-14 16:26:54 +010027file target such as BignumTarget. This test class must set/implement the
Werner Lewis2b0f7d82022-08-25 16:27:05 +010028following:
Werner Lewis008d90d2022-08-23 16:07:37 +010029 - test_function: the function name from the associated .function file.
Werner Lewisb03420f2022-08-25 12:29:46 +010030 - test_name: a descriptive name or brief summary to refer to the test
31 function.
32 - arguments(): a method to generate the list of arguments required for the
33 test_function.
34 - generate_function_test(): a method to generate TestCases for the function.
35 This should create instances of the class with required input data, and
36 call `.create_test_case()` to yield the TestCase.
Werner Lewis008d90d2022-08-23 16:07:37 +010037
38Additional details and other attributes/methods are given in the documentation
Gilles Peskine69feebd2022-09-16 21:41:47 +020039of BaseTarget in test_data_generation.py.
Werner Lewis545911f2022-07-08 13:54:57 +010040"""
41
42# Copyright The Mbed TLS Contributors
Dave Rodgman7ff79652023-11-03 12:04:52 +000043# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
Werner Lewis545911f2022-07-08 13:54:57 +010044
Werner Lewis545911f2022-07-08 13:54:57 +010045import sys
Werner Lewis008d90d2022-08-23 16:07:37 +010046
Werner Lewis47e37b32022-08-24 12:18:25 +010047from abc import ABCMeta, abstractmethod
Werner Lewis1965d482022-09-14 15:00:22 +010048from typing import Iterator, List, Tuple, TypeVar
Werner Lewis545911f2022-07-08 13:54:57 +010049
50import scripts_path # pylint: disable=unused-import
Werner Lewis545911f2022-07-08 13:54:57 +010051from mbedtls_dev import test_case
Gilles Peskine69feebd2022-09-16 21:41:47 +020052from mbedtls_dev import test_data_generation
Werner Lewis545911f2022-07-08 13:54:57 +010053
54T = TypeVar('T') #pylint: disable=invalid-name
55
Werner Lewis9509f442022-08-24 17:46:22 +010056def hex_to_int(val: str) -> int:
Gilles Peskine92c5d312022-11-09 22:06:34 +010057 """Implement the syntax accepted by mbedtls_test_read_mpi().
58
59 This is a superset of what is accepted by mbedtls_test_read_mpi_core().
60 """
Gilles Peskine83763ab2022-11-10 09:15:21 +010061 if val in ['', '-']:
Gilles Peskine92c5d312022-11-09 22:06:34 +010062 return 0
63 return int(val, 16)
Werner Lewis545911f2022-07-08 13:54:57 +010064
Werner Lewis9509f442022-08-24 17:46:22 +010065def quote_str(val) -> str:
Werner Lewis545911f2022-07-08 13:54:57 +010066 return "\"{}\"".format(val)
67
Werner Lewis38c24912022-09-14 15:12:46 +010068def combination_pairs(values: List[T]) -> List[Tuple[T, T]]:
Gilles Peskineca980c02022-09-16 22:26:38 +020069 """Return all pair combinations from input values."""
Gilles Peskineee78b6e2022-11-09 21:57:52 +010070 return [(x, y) for x in values for y in values]
Werner Lewis545911f2022-07-08 13:54:57 +010071
Gilles Peskine69feebd2022-09-16 21:41:47 +020072class BignumTarget(test_data_generation.BaseTarget, metaclass=ABCMeta):
Werner Lewisb29f59f2022-08-25 11:17:35 +010073 #pylint: disable=abstract-method
Gilles Peskine5b686082022-10-21 18:54:43 +020074 """Target for bignum (legacy) test case generation."""
75 target_basename = 'test_suite_bignum.generated'
Werner Lewis545911f2022-07-08 13:54:57 +010076
77
Werner Lewis47e37b32022-08-24 12:18:25 +010078class BignumOperation(BignumTarget, metaclass=ABCMeta):
Werner Lewisb03420f2022-08-25 12:29:46 +010079 """Common features for bignum binary operations.
Werner Lewis008d90d2022-08-23 16:07:37 +010080
81 This adds functionality common in binary operation tests. This includes
82 generation of case descriptions, using descriptions of values and symbols
83 to represent the operation or result.
Werner Lewis545911f2022-07-08 13:54:57 +010084
85 Attributes:
Werner Lewis008d90d2022-08-23 16:07:37 +010086 symbol: Symbol used for the operation in case description.
87 input_values: List of values to use as test case inputs. These are
88 combined to produce pairs of values.
Werner Lewis70d3f3d2022-08-23 14:21:53 +010089 input_cases: List of tuples containing pairs of test case inputs. This
Werner Lewis545911f2022-07-08 13:54:57 +010090 can be used to implement specific pairs of inputs.
91 """
Werner Lewis70d3f3d2022-08-23 14:21:53 +010092 symbol = ""
93 input_values = [
Gilles Peskine92c5d312022-11-09 22:06:34 +010094 "", "0", "-", "-0",
95 "7b", "-7b",
Werner Lewis545911f2022-07-08 13:54:57 +010096 "0000000000000000123", "-0000000000000000123",
97 "1230000000000000000", "-1230000000000000000"
Werner Lewisd76c5ed2022-07-20 14:13:44 +010098 ] # type: List[str]
Werner Lewis1965d482022-09-14 15:00:22 +010099 input_cases = [] # type: List[Tuple[str, str]]
Werner Lewis545911f2022-07-08 13:54:57 +0100100
Werner Lewis8b2d14b2022-09-12 17:35:27 +0100101 def __init__(self, val_a: str, val_b: str) -> None:
102 self.arg_a = val_a
103 self.arg_b = val_b
104 self.int_a = hex_to_int(val_a)
105 self.int_b = hex_to_int(val_b)
Werner Lewis545911f2022-07-08 13:54:57 +0100106
Werner Lewis9509f442022-08-24 17:46:22 +0100107 def arguments(self) -> List[str]:
Werner Lewis8b2d14b2022-09-12 17:35:27 +0100108 return [quote_str(self.arg_a), quote_str(self.arg_b), self.result()]
Werner Lewis545911f2022-07-08 13:54:57 +0100109
Gilles Peskine92c5d312022-11-09 22:06:34 +0100110 def description_suffix(self) -> str:
Gilles Peskine83763ab2022-11-10 09:15:21 +0100111 #pylint: disable=no-self-use # derived classes need self
Gilles Peskine92c5d312022-11-09 22:06:34 +0100112 """Text to add at the end of the test case description."""
113 return ""
114
Werner Lewis9509f442022-08-24 17:46:22 +0100115 def description(self) -> str:
Werner Lewis008d90d2022-08-23 16:07:37 +0100116 """Generate a description for the test case.
117
118 If not set, case_description uses the form A `symbol` B, where symbol
119 is used to represent the operation. Descriptions of each value are
120 generated to provide some context to the test case.
121 """
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100122 if not self.case_description:
123 self.case_description = "{} {} {}".format(
Werner Lewis8b2d14b2022-09-12 17:35:27 +0100124 self.value_description(self.arg_a),
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100125 self.symbol,
Werner Lewis8b2d14b2022-09-12 17:35:27 +0100126 self.value_description(self.arg_b)
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100127 )
Gilles Peskine92c5d312022-11-09 22:06:34 +0100128 description_suffix = self.description_suffix()
129 if description_suffix:
130 self.case_description += " " + description_suffix
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100131 return super().description()
Werner Lewis545911f2022-07-08 13:54:57 +0100132
Werner Lewis008d90d2022-08-23 16:07:37 +0100133 @abstractmethod
Werner Lewis47e37b32022-08-24 12:18:25 +0100134 def result(self) -> str:
Werner Lewis008d90d2022-08-23 16:07:37 +0100135 """Get the result of the operation.
136
Werner Lewis2b0f7d82022-08-25 16:27:05 +0100137 This could be calculated during initialization and stored as `_result`
138 and then returned, or calculated when the method is called.
Werner Lewis008d90d2022-08-23 16:07:37 +0100139 """
Werner Lewisd77d33d2022-08-25 09:56:51 +0100140 raise NotImplementedError
Werner Lewis545911f2022-07-08 13:54:57 +0100141
142 @staticmethod
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100143 def value_description(val) -> str:
Werner Lewis008d90d2022-08-23 16:07:37 +0100144 """Generate a description of the argument val.
145
Werner Lewis2b0f7d82022-08-25 16:27:05 +0100146 This produces a simple description of the value, which is used in test
147 case naming to add context.
Werner Lewis008d90d2022-08-23 16:07:37 +0100148 """
Werner Lewis545911f2022-07-08 13:54:57 +0100149 if val == "":
150 return "0 (null)"
Gilles Peskine92c5d312022-11-09 22:06:34 +0100151 if val == "-":
152 return "negative 0 (null)"
Werner Lewis545911f2022-07-08 13:54:57 +0100153 if val == "0":
154 return "0 (1 limb)"
155
156 if val[0] == "-":
157 tmp = "negative"
158 val = val[1:]
159 else:
160 tmp = "positive"
161 if val[0] == "0":
162 tmp += " with leading zero limb"
163 elif len(val) > 10:
164 tmp = "large " + tmp
165 return tmp
166
167 @classmethod
Werner Lewis9509f442022-08-24 17:46:22 +0100168 def get_value_pairs(cls) -> Iterator[Tuple[str, str]]:
Werner Lewisb03420f2022-08-25 12:29:46 +0100169 """Generator to yield pairs of inputs.
Werner Lewis008d90d2022-08-23 16:07:37 +0100170
171 Combinations are first generated from all input values, and then
172 specific cases provided.
173 """
Werner Lewis38c24912022-09-14 15:12:46 +0100174 yield from combination_pairs(cls.input_values)
Werner Lewis02998c42022-08-23 16:07:19 +0100175 yield from cls.input_cases
Werner Lewis545911f2022-07-08 13:54:57 +0100176
177 @classmethod
Werner Lewisc34d0372022-08-24 12:42:00 +0100178 def generate_function_tests(cls) -> Iterator[test_case.TestCase]:
Werner Lewis8b2d14b2022-09-12 17:35:27 +0100179 for a_value, b_value in cls.get_value_pairs():
180 cur_op = cls(a_value, b_value)
Werner Lewisc34d0372022-08-24 12:42:00 +0100181 yield cur_op.create_test_case()
Werner Lewis545911f2022-07-08 13:54:57 +0100182
183
184class BignumCmp(BignumOperation):
Werner Lewisb03420f2022-08-25 12:29:46 +0100185 """Test cases for bignum value comparison."""
Werner Lewis545911f2022-07-08 13:54:57 +0100186 count = 0
Gilles Peskine366e6852023-04-26 22:43:54 +0200187 test_function = "mpi_cmp_mpi"
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100188 test_name = "MPI compare"
Werner Lewis545911f2022-07-08 13:54:57 +0100189 input_cases = [
190 ("-2", "-3"),
191 ("-2", "-2"),
192 ("2b4", "2b5"),
193 ("2b5", "2b6")
194 ]
195
Werner Lewis8b2d14b2022-09-12 17:35:27 +0100196 def __init__(self, val_a, val_b) -> None:
197 super().__init__(val_a, val_b)
198 self._result = int(self.int_a > self.int_b) - int(self.int_a < self.int_b)
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100199 self.symbol = ["<", "==", ">"][self._result + 1]
Werner Lewis545911f2022-07-08 13:54:57 +0100200
Werner Lewis9509f442022-08-24 17:46:22 +0100201 def result(self) -> str:
Werner Lewis545911f2022-07-08 13:54:57 +0100202 return str(self._result)
203
204
Werner Lewis423f99b2022-07-18 15:49:43 +0100205class BignumCmpAbs(BignumCmp):
Werner Lewisb03420f2022-08-25 12:29:46 +0100206 """Test cases for absolute bignum value comparison."""
Werner Lewis423f99b2022-07-18 15:49:43 +0100207 count = 0
Gilles Peskine366e6852023-04-26 22:43:54 +0200208 test_function = "mpi_cmp_abs"
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100209 test_name = "MPI compare (abs)"
Werner Lewis423f99b2022-07-18 15:49:43 +0100210
Werner Lewis8b2d14b2022-09-12 17:35:27 +0100211 def __init__(self, val_a, val_b) -> None:
212 super().__init__(val_a.strip("-"), val_b.strip("-"))
Werner Lewis423f99b2022-07-18 15:49:43 +0100213
214
Werner Lewis5c1173b2022-07-18 17:22:58 +0100215class BignumAdd(BignumOperation):
Werner Lewisb03420f2022-08-25 12:29:46 +0100216 """Test cases for bignum value addition."""
Werner Lewis5c1173b2022-07-18 17:22:58 +0100217 count = 0
Werner Lewis46c09a62022-09-12 17:34:15 +0100218 symbol = "+"
Gilles Peskine366e6852023-04-26 22:43:54 +0200219 test_function = "mpi_add_mpi"
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100220 test_name = "MPI add"
Werner Lewis38c24912022-09-14 15:12:46 +0100221 input_cases = combination_pairs(
222 [
223 "1c67967269c6", "9cde3",
224 "-1c67967269c6", "-9cde3",
225 ]
Werner Lewis478a4ce2022-08-24 18:03:30 +0100226 )
Werner Lewis5c1173b2022-07-18 17:22:58 +0100227
Gilles Peskine92c5d312022-11-09 22:06:34 +0100228 def __init__(self, val_a: str, val_b: str) -> None:
229 super().__init__(val_a, val_b)
230 self._result = self.int_a + self.int_b
231
232 def description_suffix(self) -> str:
233 if (self.int_a >= 0 and self.int_b >= 0):
234 return "" # obviously positive result or 0
235 if (self.int_a <= 0 and self.int_b <= 0):
236 return "" # obviously negative result or 0
237 # The sign of the result is not obvious, so indicate it
238 return ", result{}0".format('>' if self._result > 0 else
239 '<' if self._result < 0 else '=')
240
Werner Lewis9509f442022-08-24 17:46:22 +0100241 def result(self) -> str:
Gilles Peskine92c5d312022-11-09 22:06:34 +0100242 return quote_str("{:x}".format(self._result))
Werner Lewis5c1173b2022-07-18 17:22:58 +0100243
Werner Lewis545911f2022-07-08 13:54:57 +0100244if __name__ == '__main__':
Werner Lewis4ed94a42022-09-16 17:03:54 +0100245 # Use the section of the docstring relevant to the CLI as description
Gilles Peskine69feebd2022-09-16 21:41:47 +0200246 test_data_generation.main(sys.argv[1:], "\n".join(__doc__.splitlines()[:4]))