diff --git a/.gitignore b/.gitignore index 06ab964..541f8de 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,7 @@ # Built documentation doc/_build/pdf/*.pdf **/doc/pdf/*.pdf + + +tests/pytest_result.xml +tests/xrun.lock diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..9132ac5 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "submodules/SP800-90B_EntropyAssessment"] + path = submodules/SP800-90B_EntropyAssessment + url = git@github.com:xmos/SP800-90B_EntropyAssessment.git diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d2ccf66 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,5 @@ +FROM gcc:15-bookworm + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + libbz2-dev libdivsufsort-dev libjsoncpp-dev libssl-dev libmpfr-dev diff --git a/Jenkinsfile b/Jenkinsfile index 7e901c6..5006a93 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,100 +1,175 @@ +// This file relates to internal XMOS infrastructure and should be ignored by external users + @Library('xmos_jenkins_shared_library@v0.43.0') _ getApproval() - pipeline { - agent none - - options { - buildDiscarder(xmosDiscardBuildSettings()) - skipDefaultCheckout() - timestamps() - } - parameters { - string( - name: 'TOOLS_VERSION', - defaultValue: '15.3.1', - description: 'The XTC tools version' - ) - string( - name: 'XMOSDOC_VERSION', - defaultValue: 'v8.0.0', - description: 'The xmosdoc version' - ) - string( - name: 'INFR_APPS_VERSION', - defaultValue: 'v3.1.1', - description: 'The infr_apps version' - ) - } + agent none - stages { - stage('🏗️ Build and test') { - agent { - label 'x86_64 && linux && documentation' - } + parameters { + string( + name: 'TOOLS_VERSION', + defaultValue: '15.3.1', + description: 'XTC tools version' + ) + string( + name: 'XMOSDOC_VERSION', + defaultValue: 'v7.4.0', + description: 'xmosdoc version' + ) + string( + name: 'INFR_APPS_VERSION', + defaultValue: 'v3.1.1', + description: 'The infr_apps version' + ) + } - stages { - stage('Build') { - steps { + options { + skipDefaultCheckout() + timestamps() + buildDiscarder(xmosDiscardBuildSettings(onlyArtifacts = false)) + } - println "Stage running on ${env.NODE_NAME}" + stages { + stage('🏗️ Build & Test') { + parallel { + stage('🏗️ Build and sim tests') { + agent { label 'x86_64 && linux && documentation' } + stages { + stage('Checkout') { + steps { + println "Stage running on ${env.NODE_NAME}" + script { + def (server, user, repo) = extractFromScmUrl() + env.REPO_NAME = repo + } + dir(REPO_NAME) { + checkoutScmShallow() + } + } + } - script { - def (server, user, repo) = extractFromScmUrl() - env.REPO_NAME = repo - } + stage('Examples build') { + steps { + dir("${REPO_NAME}/examples") { + xcoreBuild() + } + } + } - dir(REPO_NAME) { - checkoutScmShallow() + stage('Repo checks') { + steps { + warnError("Repo checks failed") { + runRepoChecks("${WORKSPACE}/${REPO_NAME}") + } + } + } - withTools(params.TOOLS_VERSION) { - dir("examples") { - xcoreBuild() - } - } - } - } - } // Build + stage('Doc build') { + steps { + dir(REPO_NAME) { + buildDocs() + } + } + } - stage('Library checks') { - steps { - warnError("Repo checks failed") { - runRepoChecks("${WORKSPACE}/${REPO_NAME}") - } - } - } + stage('Sim tests') { + steps { + dir("${REPO_NAME}/tests") { + withTools(params.TOOLS_VERSION) { + createVenv(reqFile: "requirements.txt") + withVenv { + xcoreBuild(archiveBins: false) + sh "pytest -k _xsim -vv -n auto --junitxml=pytest_result.xml" + } + } + } + junit "${REPO_NAME}/tests/**/pytest_*.xml" + } + } - stage('Documentation') { - steps { - dir(REPO_NAME) { - buildDocs() - } - } - } + stage("Archive sandbox") { + steps { + archiveSandbox(REPO_NAME) + } + } + } + post { + cleanup { + xcoreCleanSandbox() + } + } + } // stage Build and test + + stage('🏗️ Build and hardware tests') { + agent { label 'xcore.ai' } + stages { + stage('Checkout') { + steps { + println "Stage running on ${env.NODE_NAME}" + script { + def (server, user, repo) = extractFromScmUrl() + env.REPO_NAME = repo + } + dir(REPO_NAME) { + checkoutScmShallow() + sh 'git submodule update --init --recursive --depth 1' + } + } + } + stage('Build Analysis SW') { + agent { + dockerfile { + filename "${REPO_NAME}/Dockerfile" + reuseNode true + } + } + steps { + dir("${REPO_NAME}/submodules/SP800-90B_EntropyAssessment/cpp") { + sh '(cd /lib/x86_64-linux-gnu; tar cf - libdivsu*) | tar xf -' + sh 'make -k' + } + } + } - stage("Archive sandbox") { - steps { - archiveSandbox(REPO_NAME) - } + stage('Verif HW random') { + steps { + dir("${REPO_NAME}/tests") { + withTools(params.TOOLS_VERSION) { + createVenv(reqFile: "requirements.txt") + withVenv { + xcoreBuild(archiveBins: false) + sh "pytest -k _hw -vv -s test_hw.py --junitxml=pytest_result.xml" + } + } + } + junit "${REPO_NAME}/tests/**/pytest_*.xml" + } + } + + stage("Archive sandbox") { + steps { + archiveSandbox(REPO_NAME) + } + } + } + post { + cleanup { + xcoreCleanSandbox() + } + } + } // stage Test HW + } // parallel } - } // stages - post { - cleanup { - xcoreCleanSandbox() + stage('🚀 Release') { + when { + expression { triggerRelease.isReleasable() } + } + steps { + triggerRelease() + } } - } - } // stage 'Build and test' - - stage('🚀 Release') { - when { - expression { triggerRelease.isReleasable() } - } - steps { - triggerRelease() - } } - } -} +} // pipeline diff --git a/examples/app_random/src/main.c b/examples/app_random/src/main.c index fe41027..db8717b 100644 --- a/examples/app_random/src/main.c +++ b/examples/app_random/src/main.c @@ -1,4 +1,4 @@ -// Copyright 2016-2025 XMOS LIMITED. +// Copyright 2016-2026 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include #include diff --git a/lib_random/api/random.h b/lib_random/api/random.h index 18b80e1..18b6bbc 100644 --- a/lib_random/api/random.h +++ b/lib_random/api/random.h @@ -1,4 +1,4 @@ -// Copyright 2016-2025 XMOS LIMITED. +// Copyright 2016-2026 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef _RANDOM_H_ #define _RANDOM_H_ diff --git a/lib_random/src/pr_random.c b/lib_random/src/pr_random.c index fe8efd4..567a879 100644 --- a/lib_random/src/pr_random.c +++ b/lib_random/src/pr_random.c @@ -1,10 +1,10 @@ -// Copyright 2016-2025 XMOS LIMITED. +// Copyright 2016-2026 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include #include "random.h" #include "random_internal.h" -#define crc32(a,b,c) asm("crc32 %0, %1, %2" : "+r" (a) : "r" (c), "r" (b)) +#define crc32(a,b,c) asm("crc32 %0, %1, %2" : "+r" (a) : "r" (b), "r" (c)) static const unsigned random_poly = 0xEDB88320; diff --git a/lib_random/src/random_init.c b/lib_random/src/random_init.c index 0771dd9..bf526c4 100644 --- a/lib_random/src/random_init.c +++ b/lib_random/src/random_init.c @@ -1,4 +1,4 @@ -// Copyright 2016-2025 XMOS LIMITED. +// Copyright 2016-2026 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include #include "random.h" diff --git a/lib_random/src/random_internal.h b/lib_random/src/random_internal.h index 9018b56..a5de866 100644 --- a/lib_random/src/random_internal.h +++ b/lib_random/src/random_internal.h @@ -1,4 +1,4 @@ -// Copyright 2024-2025 XMOS LIMITED. +// Copyright 2024-2026 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef __random_internal_h__ diff --git a/lib_random/src/ro_random.c b/lib_random/src/ro_random.c index a3f7ed4..89a45d7 100644 --- a/lib_random/src/ro_random.c +++ b/lib_random/src/ro_random.c @@ -1,4 +1,4 @@ -// Copyright 2018-2025 XMOS LIMITED. +// Copyright 2018-2026 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include #include diff --git a/submodules/SP800-90B_EntropyAssessment b/submodules/SP800-90B_EntropyAssessment new file mode 160000 index 0000000..87c104d --- /dev/null +++ b/submodules/SP800-90B_EntropyAssessment @@ -0,0 +1 @@ +Subproject commit 87c104d0ed4cbc96103e7b8b38d6f2c7e0a6b289 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..a57af69 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.21) +include($ENV{XMOS_CMAKE_PATH}/xcommon.cmake) +project(lib_crypto_tests) + +add_subdirectory(test_pr) +add_subdirectory(test_ro) + diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..7c54b8b --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,17 @@ +# Copyright 2025-2026 XMOS LIMITED. +# This Software is subject to the terms of the XMOS Public Licence: Version 1. +import pytest + +@pytest.fixture +def level(request): + return request.config.getoption("--level") + +def pytest_addoption(parser): + + parser.addoption( + "--level", + action="store", + default="smoke", + choices=["smoke", "default", "extended"], + help="Test coverage level", + ) diff --git a/tests/requirements.txt b/tests/requirements.txt new file mode 100644 index 0000000..16d1980 --- /dev/null +++ b/tests/requirements.txt @@ -0,0 +1,37 @@ +# python_version 3.12.1 +# pip_version 24.* +# +# The parse_version_from_requirements() function in the installPipfile.groovy +# file of the Jenkins Shared Library uses the python_version comment to set +# the version of python used. + +# Distributed (released) dependencies +# +# The python modules listed below specify a known working combination required +# by the python code in this repository. The procedure used to set up a +# suitable python environment for it installs the version of each module in +# the list. Using a specific version ensures a controlled infrastructure for +# development, testing and release of this repository. +# +# Another repository might depend on python code defined in this one. The +# procedure to set up a suitable python environment for that repository may +# pip-install this one as editable using this repository's setup.py file. The +# same modules should appear in the setup.py list as given below. +pytest==8.3.3 +pytest-xdist==3.6.1 +filelock==3.19.1 + +# Development dependencies +# +# Each link listed below specifies the path to a setup.py file which are +# installed in editable mode with '-e $PATH' (without the quotes). +# +# If python code in this repository depends on python code under development +# in another repository, then an entry for that other respository should +# appear in this list instead of the released dependencies list. +# +# If this repository uses the setup functionality (e.g., script entry points) +# of its own setup.py file, then this list must include an entry for that +# setup.py file, e.g., '-e .' or '-e ./python' (without the quotes). + +-e git+ssh://git@github.com/xmos/test_support.git@v2.0.0#egg=test_support diff --git a/tests/test_hw.py b/tests/test_hw.py new file mode 100644 index 0000000..251d704 --- /dev/null +++ b/tests/test_hw.py @@ -0,0 +1,38 @@ +# Copyright 2025-2026 XMOS LIMITED. +# This Software is subject to the terms of the XMOS Public Licence: Version 1. +import re +import pytest +from pathlib import Path +from filelock import FileLock +import subprocess +import shutil +import sys + +""" +This test runs the hardware tests - none at present. Placeholder. +""" + +def test_ro(request): + test_name = "test_ro" + + cwd = Path(request.fspath).parent + binary = Path(f'{cwd}/{test_name}/bin/{test_name}.xe') + outfile= Path(f'{cwd}/{test_name}/bin/out.bin') + + assert Path(binary).exists(), f"Cannot find {binary}" + + # Ensure we don't spin up two HW instances at the same time + with FileLock("xrun.lock"): + run_cmd = f'xrun --id 0 --io --args {binary} {outfile}' + print("Running cmd: ", run_cmd) + stdout = subprocess.check_output(run_cmd, shell = True) + + run_cmd = f'LD_LIBRARY_PATH=../submodules/SP800-90B_EntropyAssessment/cpp:$LD_LIBRARY_PATH ../submodules/SP800-90B_EntropyAssessment/cpp/ea_iid {outfile}' + test_output = subprocess.check_output(run_cmd, shell = True) + print(test_output) + + assert(b'Warning' not in test_output) + assert(b'Passed chi square tests' in test_output) + assert(b'Passed length of longest repeated substring test' in test_output) + assert(b'Passed IID permutation tests' in test_output) + diff --git a/tests/test_pr/CMakeLists.txt b/tests/test_pr/CMakeLists.txt new file mode 100644 index 0000000..43bb4b4 --- /dev/null +++ b/tests/test_pr/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.21) +include($ENV{XMOS_CMAKE_PATH}/xcommon.cmake) + +project(test_pr) + +set(XMOS_SANDBOX_DIR ${CMAKE_CURRENT_LIST_DIR}/../../..) + +set(APP_HW_TARGET XK-EVK-XU316) + +set(APP_DEPENDENT_MODULES "lib_random") + +set(APP_COMPILER_FLAGS -Werror -O2 -fcmdline-buffer-bytes=1024) + +set(APP_INCLUDES src) + + +XMOS_REGISTER_APP() diff --git a/tests/test_pr/src/main.c b/tests/test_pr/src/main.c new file mode 100644 index 0000000..d41479a --- /dev/null +++ b/tests/test_pr/src/main.c @@ -0,0 +1,29 @@ +// Copyright 2025-2026 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +// This is a smoke test - just tests that there is no repeats in the first 100000 numbers. + +#include +#include +#include +#include +#include +#include +#include +#include +#include "random.h" + +int main(int argc, char *argv[]) { + random_generator_t g = 0x12345678; + + unsigned first = random_get_random_number(&g); + for(int i = 0; i < 100000; i++) { + unsigned x = random_get_random_number(&g); + if (x == first) { + printf("FAIL %d %08x %08x\n", i, first, x); + exit(1); + } + } + printf("PASS\n"); + return 0; +} diff --git a/tests/test_ro/CMakeLists.txt b/tests/test_ro/CMakeLists.txt new file mode 100644 index 0000000..a635966 --- /dev/null +++ b/tests/test_ro/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.21) +include($ENV{XMOS_CMAKE_PATH}/xcommon.cmake) + +project(test_ro) + +set(XMOS_SANDBOX_DIR ${CMAKE_CURRENT_LIST_DIR}/../../..) + +set(APP_HW_TARGET XK-EVK-XU316) + +set(APP_DEPENDENT_MODULES "lib_random") + +set(APP_COMPILER_FLAGS -Werror -O2 -fcmdline-buffer-bytes=1024 -mcmodel=large) + +set(APP_INCLUDES src) + + +XMOS_REGISTER_APP() diff --git a/tests/test_ro/src/main.c b/tests/test_ro/src/main.c new file mode 100644 index 0000000..9a143ab --- /dev/null +++ b/tests/test_ro/src/main.c @@ -0,0 +1,46 @@ +// Copyright 2025-2026 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +// This program collects 1,000,000 random bits +// These are stored compressed, and then written to a file as single bits in a byte file +// These can then be analysed off-line for randomness. + +#include +#include +#include +#include +#include +#include "random.h" + +#define N 1000000/32 + +unsigned int data[N]; + +int main(int argc, char *argv[]) { + int t0 = get_reference_time(); + random_ro_init(); + for(int i = 0; i < N; i++) { + int j = 0; + unsigned int bits = 0; + do { + int bit = random_ro_get_bit(); + if (bit >= 0) { + bits |= bit << j; + j++; + } + } while (j != 32); + data[i] = bits; + } + int t1 = get_reference_time(); + printf("%d %d\n", t1 - t0, (t1-t0)/(N*32)); + FILE *fd = fopen(argv[1], "wb"); + for(int i = 0; i < N; i++) { + unsigned char output_data[32]; + for(int j = 0; j < 32; j++) { + output_data[j] = (data[i] >> j)&1; + } + fwrite(output_data, sizeof(output_data), 1, fd); + } + fclose(fd); + return 0; +} diff --git a/tests/test_xsim.py b/tests/test_xsim.py new file mode 100644 index 0000000..fadd82c --- /dev/null +++ b/tests/test_xsim.py @@ -0,0 +1,13 @@ +# Copyright 2025-2026 XMOS LIMITED. +# This Software is subject to the terms of the XMOS Public Licence: Version 1. + +from pathlib import Path +import subprocess +import sys + +def test_xsim(request): + bin_path = Path(__file__).parent / "test_pr" / "bin" + run_cmd = "xsim " + "--args " + str(bin_path) + f"/test_pr.xe" + stdout = subprocess.check_output(run_cmd, cwd = bin_path, shell = True) + print(stdout) + assert(b'PASS\n' == stdout)