diff --git a/scripts/mbedtls_dev/bignum_common.py b/scripts/mbedtls_dev/bignum_common.py new file mode 100644 index 000000000..b20fe815d --- /dev/null +++ b/scripts/mbedtls_dev/bignum_common.py @@ -0,0 +1,111 @@ +"""Common features for bignum in test generation framework.""" +# Copyright The Mbed TLS Contributors +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import itertools +import typing + +from abc import abstractmethod +from typing import Iterator, List, Tuple, TypeVar + +T = TypeVar('T') #pylint: disable=invalid-name + +def hex_to_int(val: str) -> int: + return int(val, 16) if val else 0 + +def quote_str(val) -> str: + return "\"{}\"".format(val) + +def bound_mpi8(val: int) -> int: + """First number exceeding 8-byte limbs needed for given input value.""" + return bound_mpi8_limbs(limbs_mpi8(val)) + +def bound_mpi4(val: int) -> int: + """First number exceeding 4-byte limbs needed for given input value.""" + return bound_mpi4_limbs(limbs_mpi4(val)) + +def bound_mpi8_limbs(limbs: int) -> int: + """First number exceeding maximum of given 8-byte limbs.""" + bits = 64 * limbs + return 1 << bits + +def bound_mpi4_limbs(limbs: int) -> int: + """First number exceeding maximum of given 4-byte limbs.""" + bits = 32 * limbs + return 1 << bits + +def limbs_mpi8(val: int) -> int: + """Return the number of 8-byte limbs required to store value.""" + return (val.bit_length() + 63) // 64 + +def limbs_mpi4(val: int) -> int: + """Return the number of 4-byte limbs required to store value.""" + return (val.bit_length() + 31) // 32 + +def combination_pairs(values: List[T]) -> List[Tuple[T, T]]: + """Return all pair combinations from input values. + + The return value is cast, as older versions of mypy are unable to derive + the specific type returned by itertools.combinations_with_replacement. + """ + return typing.cast( + List[Tuple[T, T]], + list(itertools.combinations_with_replacement(values, 2)) + ) + + +class OperationCommon: + """Common features for bignum binary operations. + + This adds functionality common in binary operation tests. + + Attributes: + symbol: Symbol to use for the operation in case description. + input_values: List of values to use as test case inputs. These are + combined to produce pairs of values. + input_cases: List of tuples containing pairs of test case inputs. This + can be used to implement specific pairs of inputs. + """ + symbol = "" + input_values = [] # type: List[str] + input_cases = [] # type: List[Tuple[str, str]] + + def __init__(self, val_a: str, val_b: str) -> None: + self.arg_a = val_a + self.arg_b = val_b + self.int_a = hex_to_int(val_a) + self.int_b = hex_to_int(val_b) + + def arguments(self) -> List[str]: + return [quote_str(self.arg_a), quote_str(self.arg_b), self.result()] + + @abstractmethod + def result(self) -> str: + """Get the result of the operation. + + This could be calculated during initialization and stored as `_result` + and then returned, or calculated when the method is called. + """ + raise NotImplementedError + + @classmethod + def get_value_pairs(cls) -> Iterator[Tuple[str, str]]: + """Generator to yield pairs of inputs. + + Combinations are first generated from all input values, and then + specific cases provided. + """ + yield from combination_pairs(cls.input_values) + yield from cls.input_cases diff --git a/scripts/mbedtls_dev/bignum_core.py b/scripts/mbedtls_dev/bignum_core.py new file mode 100644 index 000000000..b30f8581a --- /dev/null +++ b/scripts/mbedtls_dev/bignum_core.py @@ -0,0 +1,72 @@ +"""Framework classes for generation of bignum core test cases.""" +# Copyright The Mbed TLS Contributors +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from abc import ABCMeta +from typing import Iterator + +from . import test_case +from . import test_data_generation +from . import bignum_common + + +class BignumCoreTarget(test_data_generation.BaseTarget, metaclass=ABCMeta): + #pylint: disable=abstract-method + """Target for bignum core test case generation.""" + target_basename = 'test_suite_bignum_core.generated' + + +class BignumCoreOperation(bignum_common.OperationCommon, BignumCoreTarget, metaclass=ABCMeta): + #pylint: disable=abstract-method + """Common features for bignum core operations.""" + input_values = [ + "0", "1", "3", "f", "fe", "ff", "100", "ff00", "fffe", "ffff", "10000", + "fffffffe", "ffffffff", "100000000", "1f7f7f7f7f7f7f", + "8000000000000000", "fefefefefefefefe", "fffffffffffffffe", + "ffffffffffffffff", "10000000000000000", "1234567890abcdef0", + "fffffffffffffffffefefefefefefefe", "fffffffffffffffffffffffffffffffe", + "ffffffffffffffffffffffffffffffff", "100000000000000000000000000000000", + "1234567890abcdef01234567890abcdef0", + "fffffffffffffffffffffffffffffffffffffffffffffffffefefefefefefefe", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe", + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "10000000000000000000000000000000000000000000000000000000000000000", + "1234567890abcdef01234567890abcdef01234567890abcdef01234567890abcdef0", + ( + "4df72d07b4b71c8dacb6cffa954f8d88254b6277099308baf003fab73227f34029" + "643b5a263f66e0d3c3fa297ef71755efd53b8fb6cb812c6bbf7bcf179298bd9947" + "c4c8b14324140a2c0f5fad7958a69050a987a6096e9f055fb38edf0c5889eca4a0" + "cfa99b45fbdeee4c696b328ddceae4723945901ec025076b12b" + ) + ] + + def description(self) -> str: + """Generate a description for the test case. + + If not set, case_description uses the form A `symbol` B, where symbol + is used to represent the operation. Descriptions of each value are + generated to provide some context to the test case. + """ + if not self.case_description: + self.case_description = "{} {} {}".format( + self.arg_a, self.symbol, self.arg_b + ) + return super().description() + + @classmethod + def generate_function_tests(cls) -> Iterator[test_case.TestCase]: + for a_value, b_value in cls.get_value_pairs(): + yield cls(a_value, b_value).create_test_case() + diff --git a/scripts/mbedtls_dev/test_data_generation.py b/scripts/mbedtls_dev/test_data_generation.py index cdb1c03b8..eec0f9d97 100644 --- a/scripts/mbedtls_dev/test_data_generation.py +++ b/scripts/mbedtls_dev/test_data_generation.py @@ -148,6 +148,7 @@ class TestGenerator: self.targets.update({ subclass.target_basename: subclass.generate_tests for subclass in BaseTarget.__subclasses__() + if subclass.target_basename }) def filename_for(self, basename: str) -> str: diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d89542a44..c23cb6b3d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -68,6 +68,8 @@ if(GEN_FILES) --directory ${CMAKE_CURRENT_BINARY_DIR}/suites DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/../tests/scripts/generate_bignum_tests.py + ${CMAKE_CURRENT_SOURCE_DIR}/../scripts/mbedtls_dev/bignum_common.py + ${CMAKE_CURRENT_SOURCE_DIR}/../scripts/mbedtls_dev/bignum_core.py ${CMAKE_CURRENT_SOURCE_DIR}/../scripts/mbedtls_dev/test_case.py ${CMAKE_CURRENT_SOURCE_DIR}/../scripts/mbedtls_dev/test_data_generation.py ) diff --git a/tests/Makefile b/tests/Makefile index 57f885544..7c08f54e1 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -92,6 +92,8 @@ generated_files: $(GENERATED_FILES) .SECONDARY: generated_bignum_test_data generated_psa_test_data $(GENERATED_BIGNUM_DATA_FILES): generated_bignum_test_data generated_bignum_test_data: scripts/generate_bignum_tests.py +generated_bignum_test_data: ../scripts/mbedtls_dev/bignum_common.py +generated_bignum_test_data: ../scripts/mbedtls_dev/bignum_core.py generated_bignum_test_data: ../scripts/mbedtls_dev/test_case.py generated_bignum_test_data: ../scripts/mbedtls_dev/test_data_generation.py generated_bignum_test_data: diff --git a/tests/scripts/generate_bignum_tests.py b/tests/scripts/generate_bignum_tests.py index 7f332dca0..1eec27b10 100755 --- a/tests/scripts/generate_bignum_tests.py +++ b/tests/scripts/generate_bignum_tests.py @@ -54,34 +54,19 @@ of BaseTarget in test_data_generation.py. # See the License for the specific language governing permissions and # limitations under the License. -import itertools import sys -import typing -from abc import ABCMeta, abstractmethod -from typing import Iterator, List, Tuple, TypeVar +from abc import ABCMeta +from typing import Iterator import scripts_path # pylint: disable=unused-import from mbedtls_dev import test_case from mbedtls_dev import test_data_generation - -T = TypeVar('T') #pylint: disable=invalid-name - -def hex_to_int(val: str) -> int: - return int(val, 16) if val else 0 - -def quote_str(val) -> str: - return "\"{}\"".format(val) - -def combination_pairs(values: List[T]) -> List[Tuple[T, T]]: - """Return all pair combinations from input values.""" - # The return value is cast, as older versions of mypy are unable to derive - # the specific type returned by itertools.combinations_with_replacement. - return typing.cast( - List[Tuple[T, T]], - list(itertools.combinations_with_replacement(values, 2)) - ) - +from mbedtls_dev import bignum_common +# Import modules containing additional test classes +# Test function classes in these modules will be registered by +# the framework +from mbedtls_dev import bignum_core # pylint: disable=unused-import class BignumTarget(test_data_generation.BaseTarget, metaclass=ABCMeta): #pylint: disable=abstract-method @@ -89,36 +74,14 @@ class BignumTarget(test_data_generation.BaseTarget, metaclass=ABCMeta): target_basename = 'test_suite_mpi.generated' -class BignumOperation(BignumTarget, metaclass=ABCMeta): - """Common features for bignum binary operations. - - This adds functionality common in binary operation tests. This includes - generation of case descriptions, using descriptions of values and symbols - to represent the operation or result. - - Attributes: - symbol: Symbol used for the operation in case description. - input_values: List of values to use as test case inputs. These are - combined to produce pairs of values. - input_cases: List of tuples containing pairs of test case inputs. This - can be used to implement specific pairs of inputs. - """ - symbol = "" +class BignumOperation(bignum_common.OperationCommon, BignumTarget, metaclass=ABCMeta): + #pylint: disable=abstract-method + """Common features for bignum operations in legacy tests.""" input_values = [ "", "0", "7b", "-7b", "0000000000000000123", "-0000000000000000123", "1230000000000000000", "-1230000000000000000" - ] # type: List[str] - input_cases = [] # type: List[Tuple[str, str]] - - def __init__(self, val_a: str, val_b: str) -> None: - self.arg_a = val_a - self.arg_b = val_b - self.int_a = hex_to_int(val_a) - self.int_b = hex_to_int(val_b) - - def arguments(self) -> List[str]: - return [quote_str(self.arg_a), quote_str(self.arg_b), self.result()] + ] def description(self) -> str: """Generate a description for the test case. @@ -135,15 +98,6 @@ class BignumOperation(BignumTarget, metaclass=ABCMeta): ) return super().description() - @abstractmethod - def result(self) -> str: - """Get the result of the operation. - - This could be calculated during initialization and stored as `_result` - and then returned, or calculated when the method is called. - """ - raise NotImplementedError - @staticmethod def value_description(val) -> str: """Generate a description of the argument val. @@ -167,21 +121,10 @@ class BignumOperation(BignumTarget, metaclass=ABCMeta): tmp = "large " + tmp return tmp - @classmethod - def get_value_pairs(cls) -> Iterator[Tuple[str, str]]: - """Generator to yield pairs of inputs. - - Combinations are first generated from all input values, and then - specific cases provided. - """ - yield from combination_pairs(cls.input_values) - yield from cls.input_cases - @classmethod def generate_function_tests(cls) -> Iterator[test_case.TestCase]: for a_value, b_value in cls.get_value_pairs(): - cur_op = cls(a_value, b_value) - yield cur_op.create_test_case() + yield cls(a_value, b_value).create_test_case() class BignumCmp(BignumOperation): @@ -221,7 +164,7 @@ class BignumAdd(BignumOperation): symbol = "+" test_function = "mpi_add_mpi" test_name = "MPI add" - input_cases = combination_pairs( + input_cases = bignum_common.combination_pairs( [ "1c67967269c6", "9cde3", "-1c67967269c6", "-9cde3", @@ -229,7 +172,8 @@ class BignumAdd(BignumOperation): ) def result(self) -> str: - return quote_str("{:x}".format(self.int_a + self.int_b)) + return bignum_common.quote_str("{:x}").format(self.int_a + self.int_b) + if __name__ == '__main__': # Use the section of the docstring relevant to the CLI as description