blob: fd677aee7183b0d1f3318b5aab71084ce5e6bba9 [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
43# SPDX-License-Identifier: Apache-2.0
44#
45# Licensed under the Apache License, Version 2.0 (the "License"); you may
46# not use this file except in compliance with the License.
47# You may obtain a copy of the License at
48#
49# http://www.apache.org/licenses/LICENSE-2.0
50#
51# Unless required by applicable law or agreed to in writing, software
52# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
53# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
54# See the License for the specific language governing permissions and
55# limitations under the License.
56
Werner Lewis545911f2022-07-08 13:54:57 +010057import sys
Werner Lewis008d90d2022-08-23 16:07:37 +010058
Werner Lewis47e37b32022-08-24 12:18:25 +010059from abc import ABCMeta, abstractmethod
Werner Lewis1965d482022-09-14 15:00:22 +010060from typing import Iterator, List, Tuple, TypeVar
Werner Lewis545911f2022-07-08 13:54:57 +010061
62import scripts_path # pylint: disable=unused-import
Werner Lewis545911f2022-07-08 13:54:57 +010063from mbedtls_dev import test_case
Gilles Peskine69feebd2022-09-16 21:41:47 +020064from mbedtls_dev import test_data_generation
Werner Lewis545911f2022-07-08 13:54:57 +010065
66T = TypeVar('T') #pylint: disable=invalid-name
67
Werner Lewis9509f442022-08-24 17:46:22 +010068def hex_to_int(val: str) -> int:
Gilles Peskine92c5d312022-11-09 22:06:34 +010069 """Implement the syntax accepted by mbedtls_test_read_mpi().
70
71 This is a superset of what is accepted by mbedtls_test_read_mpi_core().
72 """
Gilles Peskine83763ab2022-11-10 09:15:21 +010073 if val in ['', '-']:
Gilles Peskine92c5d312022-11-09 22:06:34 +010074 return 0
75 return int(val, 16)
Werner Lewis545911f2022-07-08 13:54:57 +010076
Werner Lewis9509f442022-08-24 17:46:22 +010077def quote_str(val) -> str:
Werner Lewis545911f2022-07-08 13:54:57 +010078 return "\"{}\"".format(val)
79
Werner Lewis38c24912022-09-14 15:12:46 +010080def combination_pairs(values: List[T]) -> List[Tuple[T, T]]:
Gilles Peskineca980c02022-09-16 22:26:38 +020081 """Return all pair combinations from input values."""
Gilles Peskineee78b6e2022-11-09 21:57:52 +010082 return [(x, y) for x in values for y in values]
Werner Lewis545911f2022-07-08 13:54:57 +010083
Gilles Peskine69feebd2022-09-16 21:41:47 +020084class BignumTarget(test_data_generation.BaseTarget, metaclass=ABCMeta):
Werner Lewisb29f59f2022-08-25 11:17:35 +010085 #pylint: disable=abstract-method
Gilles Peskine5b686082022-10-21 18:54:43 +020086 """Target for bignum (legacy) test case generation."""
87 target_basename = 'test_suite_bignum.generated'
Werner Lewis545911f2022-07-08 13:54:57 +010088
89
Werner Lewis47e37b32022-08-24 12:18:25 +010090class BignumOperation(BignumTarget, metaclass=ABCMeta):
Werner Lewisb03420f2022-08-25 12:29:46 +010091 """Common features for bignum binary operations.
Werner Lewis008d90d2022-08-23 16:07:37 +010092
93 This adds functionality common in binary operation tests. This includes
94 generation of case descriptions, using descriptions of values and symbols
95 to represent the operation or result.
Werner Lewis545911f2022-07-08 13:54:57 +010096
97 Attributes:
Werner Lewis008d90d2022-08-23 16:07:37 +010098 symbol: Symbol used for the operation in case description.
99 input_values: List of values to use as test case inputs. These are
100 combined to produce pairs of values.
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100101 input_cases: List of tuples containing pairs of test case inputs. This
Werner Lewis545911f2022-07-08 13:54:57 +0100102 can be used to implement specific pairs of inputs.
103 """
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100104 symbol = ""
105 input_values = [
Gilles Peskine92c5d312022-11-09 22:06:34 +0100106 "", "0", "-", "-0",
107 "7b", "-7b",
Werner Lewis545911f2022-07-08 13:54:57 +0100108 "0000000000000000123", "-0000000000000000123",
109 "1230000000000000000", "-1230000000000000000"
Werner Lewisd76c5ed2022-07-20 14:13:44 +0100110 ] # type: List[str]
Werner Lewis1965d482022-09-14 15:00:22 +0100111 input_cases = [] # type: List[Tuple[str, str]]
Werner Lewis545911f2022-07-08 13:54:57 +0100112
Werner Lewis8b2d14b2022-09-12 17:35:27 +0100113 def __init__(self, val_a: str, val_b: str) -> None:
114 self.arg_a = val_a
115 self.arg_b = val_b
116 self.int_a = hex_to_int(val_a)
117 self.int_b = hex_to_int(val_b)
Werner Lewis545911f2022-07-08 13:54:57 +0100118
Werner Lewis9509f442022-08-24 17:46:22 +0100119 def arguments(self) -> List[str]:
Werner Lewis8b2d14b2022-09-12 17:35:27 +0100120 return [quote_str(self.arg_a), quote_str(self.arg_b), self.result()]
Werner Lewis545911f2022-07-08 13:54:57 +0100121
Gilles Peskine92c5d312022-11-09 22:06:34 +0100122 def description_suffix(self) -> str:
Gilles Peskine83763ab2022-11-10 09:15:21 +0100123 #pylint: disable=no-self-use # derived classes need self
Gilles Peskine92c5d312022-11-09 22:06:34 +0100124 """Text to add at the end of the test case description."""
125 return ""
126
Werner Lewis9509f442022-08-24 17:46:22 +0100127 def description(self) -> str:
Werner Lewis008d90d2022-08-23 16:07:37 +0100128 """Generate a description for the test case.
129
130 If not set, case_description uses the form A `symbol` B, where symbol
131 is used to represent the operation. Descriptions of each value are
132 generated to provide some context to the test case.
133 """
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100134 if not self.case_description:
135 self.case_description = "{} {} {}".format(
Werner Lewis8b2d14b2022-09-12 17:35:27 +0100136 self.value_description(self.arg_a),
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100137 self.symbol,
Werner Lewis8b2d14b2022-09-12 17:35:27 +0100138 self.value_description(self.arg_b)
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100139 )
Gilles Peskine92c5d312022-11-09 22:06:34 +0100140 description_suffix = self.description_suffix()
141 if description_suffix:
142 self.case_description += " " + description_suffix
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100143 return super().description()
Werner Lewis545911f2022-07-08 13:54:57 +0100144
Werner Lewis008d90d2022-08-23 16:07:37 +0100145 @abstractmethod
Werner Lewis47e37b32022-08-24 12:18:25 +0100146 def result(self) -> str:
Werner Lewis008d90d2022-08-23 16:07:37 +0100147 """Get the result of the operation.
148
Werner Lewis2b0f7d82022-08-25 16:27:05 +0100149 This could be calculated during initialization and stored as `_result`
150 and then returned, or calculated when the method is called.
Werner Lewis008d90d2022-08-23 16:07:37 +0100151 """
Werner Lewisd77d33d2022-08-25 09:56:51 +0100152 raise NotImplementedError
Werner Lewis545911f2022-07-08 13:54:57 +0100153
154 @staticmethod
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100155 def value_description(val) -> str:
Werner Lewis008d90d2022-08-23 16:07:37 +0100156 """Generate a description of the argument val.
157
Werner Lewis2b0f7d82022-08-25 16:27:05 +0100158 This produces a simple description of the value, which is used in test
159 case naming to add context.
Werner Lewis008d90d2022-08-23 16:07:37 +0100160 """
Werner Lewis545911f2022-07-08 13:54:57 +0100161 if val == "":
162 return "0 (null)"
Gilles Peskine92c5d312022-11-09 22:06:34 +0100163 if val == "-":
164 return "negative 0 (null)"
Werner Lewis545911f2022-07-08 13:54:57 +0100165 if val == "0":
166 return "0 (1 limb)"
167
168 if val[0] == "-":
169 tmp = "negative"
170 val = val[1:]
171 else:
172 tmp = "positive"
173 if val[0] == "0":
174 tmp += " with leading zero limb"
175 elif len(val) > 10:
176 tmp = "large " + tmp
177 return tmp
178
179 @classmethod
Werner Lewis9509f442022-08-24 17:46:22 +0100180 def get_value_pairs(cls) -> Iterator[Tuple[str, str]]:
Werner Lewisb03420f2022-08-25 12:29:46 +0100181 """Generator to yield pairs of inputs.
Werner Lewis008d90d2022-08-23 16:07:37 +0100182
183 Combinations are first generated from all input values, and then
184 specific cases provided.
185 """
Werner Lewis38c24912022-09-14 15:12:46 +0100186 yield from combination_pairs(cls.input_values)
Werner Lewis02998c42022-08-23 16:07:19 +0100187 yield from cls.input_cases
Werner Lewis545911f2022-07-08 13:54:57 +0100188
189 @classmethod
Werner Lewisc34d0372022-08-24 12:42:00 +0100190 def generate_function_tests(cls) -> Iterator[test_case.TestCase]:
Werner Lewis8b2d14b2022-09-12 17:35:27 +0100191 for a_value, b_value in cls.get_value_pairs():
192 cur_op = cls(a_value, b_value)
Werner Lewisc34d0372022-08-24 12:42:00 +0100193 yield cur_op.create_test_case()
Werner Lewis545911f2022-07-08 13:54:57 +0100194
195
196class BignumCmp(BignumOperation):
Werner Lewisb03420f2022-08-25 12:29:46 +0100197 """Test cases for bignum value comparison."""
Werner Lewis545911f2022-07-08 13:54:57 +0100198 count = 0
Gilles Peskine366e6852023-04-26 22:43:54 +0200199 test_function = "mpi_cmp_mpi"
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100200 test_name = "MPI compare"
Werner Lewis545911f2022-07-08 13:54:57 +0100201 input_cases = [
202 ("-2", "-3"),
203 ("-2", "-2"),
204 ("2b4", "2b5"),
205 ("2b5", "2b6")
206 ]
207
Werner Lewis8b2d14b2022-09-12 17:35:27 +0100208 def __init__(self, val_a, val_b) -> None:
209 super().__init__(val_a, val_b)
210 self._result = int(self.int_a > self.int_b) - int(self.int_a < self.int_b)
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100211 self.symbol = ["<", "==", ">"][self._result + 1]
Werner Lewis545911f2022-07-08 13:54:57 +0100212
Werner Lewis9509f442022-08-24 17:46:22 +0100213 def result(self) -> str:
Werner Lewis545911f2022-07-08 13:54:57 +0100214 return str(self._result)
215
216
Werner Lewis423f99b2022-07-18 15:49:43 +0100217class BignumCmpAbs(BignumCmp):
Werner Lewisb03420f2022-08-25 12:29:46 +0100218 """Test cases for absolute bignum value comparison."""
Werner Lewis423f99b2022-07-18 15:49:43 +0100219 count = 0
Gilles Peskine366e6852023-04-26 22:43:54 +0200220 test_function = "mpi_cmp_abs"
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100221 test_name = "MPI compare (abs)"
Werner Lewis423f99b2022-07-18 15:49:43 +0100222
Werner Lewis8b2d14b2022-09-12 17:35:27 +0100223 def __init__(self, val_a, val_b) -> None:
224 super().__init__(val_a.strip("-"), val_b.strip("-"))
Werner Lewis423f99b2022-07-18 15:49:43 +0100225
226
Werner Lewis5c1173b2022-07-18 17:22:58 +0100227class BignumAdd(BignumOperation):
Werner Lewisb03420f2022-08-25 12:29:46 +0100228 """Test cases for bignum value addition."""
Werner Lewis5c1173b2022-07-18 17:22:58 +0100229 count = 0
Werner Lewis46c09a62022-09-12 17:34:15 +0100230 symbol = "+"
Gilles Peskine366e6852023-04-26 22:43:54 +0200231 test_function = "mpi_add_mpi"
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100232 test_name = "MPI add"
Werner Lewis38c24912022-09-14 15:12:46 +0100233 input_cases = combination_pairs(
234 [
235 "1c67967269c6", "9cde3",
236 "-1c67967269c6", "-9cde3",
237 ]
Werner Lewis478a4ce2022-08-24 18:03:30 +0100238 )
Werner Lewis5c1173b2022-07-18 17:22:58 +0100239
Gilles Peskine92c5d312022-11-09 22:06:34 +0100240 def __init__(self, val_a: str, val_b: str) -> None:
241 super().__init__(val_a, val_b)
242 self._result = self.int_a + self.int_b
243
244 def description_suffix(self) -> str:
245 if (self.int_a >= 0 and self.int_b >= 0):
246 return "" # obviously positive result or 0
247 if (self.int_a <= 0 and self.int_b <= 0):
248 return "" # obviously negative result or 0
249 # The sign of the result is not obvious, so indicate it
250 return ", result{}0".format('>' if self._result > 0 else
251 '<' if self._result < 0 else '=')
252
Werner Lewis9509f442022-08-24 17:46:22 +0100253 def result(self) -> str:
Gilles Peskine92c5d312022-11-09 22:06:34 +0100254 return quote_str("{:x}".format(self._result))
Werner Lewis5c1173b2022-07-18 17:22:58 +0100255
Werner Lewis545911f2022-07-08 13:54:57 +0100256if __name__ == '__main__':
Werner Lewis4ed94a42022-09-16 17:03:54 +0100257 # Use the section of the docstring relevant to the CLI as description
Gilles Peskine69feebd2022-09-16 21:41:47 +0200258 test_data_generation.main(sys.argv[1:], "\n".join(__doc__.splitlines()[:4]))