-
Notifications
You must be signed in to change notification settings - Fork 43
Add support for TF-PSA-Crypto test driver #243
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Update test drivers for TF-PSA-Crypto test driver based on a copy of `drivers/builtin`. We mainly keep using what is already in place for libtestdriver1 in Mbed TLS by still defining MBEDTLS_TEST_LIBTESTDRIVER1. As in TF-PSA-Crypto, the test driver is a derivative of a clone of the built-in driver, not of the whole library, the paths to the driver internal headers are different. The paths are relative to `drivers/libtestdriver1/include`. Otherwise, `psa_key_attributes_t` is prefixed with `libtestdriver1_` in Mbed TLS test driver but not in TF-PSA-Crypto one. Thus in TF-PSA-Crypto case define `libtestdriver1_psa_key_attributes_t` as `psa_key_attributes_t`. Signed-off-by: Ronald Cron <[email protected]>
Signed-off-by: Ronald Cron <[email protected]>
Signed-off-by: Ronald Cron <[email protected]>
c01d250 to
61f15a6
Compare
Add get_identifiers_with_prefixes method that returns the list of identifiers that potentially need to be prefixed in the test driver code. Signed-off-by: Ronald Cron <[email protected]>
Signed-off-by: Ronald Cron <[email protected]>
61f15a6 to
6ffca2e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've fully reviewed 61f15a6, as well as the Python code in Mbed-TLS/TF-PSA-Crypto#586.
I like the methodology, but the Python code sometimes feels hard to follow compared to what it does.
|
|
||
| #if defined(MBEDTLS_TEST_LIBTESTDRIVER1) | ||
| #if defined(TF_PSA_CRYPTO_TEST_LIBTESTDRIVER1) | ||
| typedef psa_key_attributes_t libtestdriver1_psa_key_attributes_t; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is repeated, it feels like it should be in a header somewhere, probably tests/include/test/drivers/test_driver_common.h.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @gilles-peskine-arm
What the code is effectively trying to achieve is rewrite the include path and include the appropriate header depending on the variant of the libtestdriver.
That logic alongside the typedef could easily reside in a separate header and exposed thought a getter macro e.g LIBTESTDRIVER1_INCLUDE(psa_crypto_hash)
| #if defined(MBEDTLS_TEST_LIBTESTDRIVER1) | ||
| #if defined(TF_PSA_CRYPTO_TEST_LIBTESTDRIVER1) | ||
| typedef psa_key_attributes_t libtestdriver1_psa_key_attributes_t; | ||
| #include "../../libtestdriver1/src/psa_crypto_aead.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It took me a while to understand this path. It's weird to go up two levels. My first thought was that the include path was relative to the file that this directive is in, but that didn't work, and a path relative to the framework would be fragile since it could be different in in-tree vs out-of-tree builds. Then I noticed the commit message
The paths are relative to
drivers/libtestdriver1/include.
Ok, that makes sense, but then why not ../src/psa_crypto_aead.h? But that wouldn't necessarily work since it could pick up the one under /drivers/builtin. Ok, then it does have to be what you wrote, although I would prefer
#include <../../libtestdriver1/src/psa_crypto_aead.h>
since we know this isn't relative to the location of the file, it comes from the include path.
And I would like this explained in a comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1. When people will need to touch the code in the future they likely wont consider looking at the comment history using git log --follow. If its not intuitive it should be a comment
| parser = argparse.ArgumentParser(description= \ | ||
| "Clone partially builtin tree, rewrite header inclusions and prefix" | ||
| "exposed C identifiers.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The string contains prefixexposed without a space. Use triple-quotes for such long strings.
| parser = argparse.ArgumentParser(description= \ | |
| "Clone partially builtin tree, rewrite header inclusions and prefix" | |
| "exposed C identifiers.") | |
| parser = argparse.ArgumentParser(description="""\ | |
| Partially clone builtin tree, rewrite header inclusions and prefix exposed C | |
| identifiers. | |
| """) |
(I also fixed the grammar, since AFAICT this isn't about cloning trees that are partially built-in, but about partially cloning the builtin tree.)
| help="Destination directory.\n" | ||
| " - If absolute, used as-is.\n" | ||
| " - If relative, interpreted relative to the repository root.\n") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
argparse doesn't preserve line breaks in help text, so you can't format with bullet points. I think it could be shorter anyway.
| help="Destination directory.\n" | |
| " - If absolute, used as-is.\n" | |
| " - If relative, interpreted relative to the repository root.\n") | |
| help="Destination directory (relative to repository root)" |
| for directory in ("include", "src"): | ||
| directory_path = root / directory | ||
| for ext in (".c", ".h"): | ||
| yield from directory_path.rglob(f"*{ext}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rglob doesn't have deterministic ordering, which can make debugging annoying. On the other hand, you take care to emit .c files before .h files, is there a reason for that?
How about something like
return sorted(path
for directory in ('include', 'src')
for path in (root / directory).rglob('*.[hc]')
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No particular reason to use "for ext in (".c", ".h"):" over "*.[hc]".
| #include "libtestdriver1/private/aes.h" | ||
| """ | ||
| include_line_re = re.compile( | ||
| fr'^\s*#\s*include\s*([<"])\s*{src_include_dir}/([^>"]+)\s*([>"])', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can't have spaces inside the quotes/brackets.
| fr'^\s*#\s*include\s*([<"])\s*{src_include_dir}/([^>"]+)\s*([>"])', | |
| fr'^\s*#\s*include\s*([<"]){src_include_dir}/([^>"]+)([>"])', |
| return f'#include {m.group(1)}{driver}/{header}{m.group(3)}' | ||
| return m.group(0) | ||
|
|
||
| new_text = include_line_re.sub(repl, text) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be simplified to something like (untested):
| new_text = include_line_re.sub(repl, text) | |
| renamed_headers = '|'.join(re.escape(header) for header in headers) | |
| include_line_re = re.compile(fr'^(\s*#\s*include\s*["<])((?:{renamed_headers})[">])', re.M) | |
| new_text, changed = include_line_re.subn(repl, fr'\1{driver}-\2') |
| c_identifier_re = re.compile(r"\b[A-Za-z_][A-Za-z0-9_]*\b") | ||
| text = file.read_text(encoding="utf-8") | ||
| prefix_uppercased = prefix.upper() | ||
| prefix_lowercased = prefix.lower() | ||
|
|
||
| def repl(m: Match) -> str: | ||
| identifier = m.group(0) | ||
| if identifier in identifiers: | ||
| if identifier[0].isupper(): | ||
| return f"{prefix_uppercased}_{identifier}" | ||
| else: | ||
| return f"{prefix_lowercased}_{identifier}" | ||
| return identifier | ||
|
|
||
| new_text = c_identifier_re.sub(repl, text) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can make regexps do the work:
| c_identifier_re = re.compile(r"\b[A-Za-z_][A-Za-z0-9_]*\b") | |
| text = file.read_text(encoding="utf-8") | |
| prefix_uppercased = prefix.upper() | |
| prefix_lowercased = prefix.lower() | |
| def repl(m: Match) -> str: | |
| identifier = m.group(0) | |
| if identifier in identifiers: | |
| if identifier[0].isupper(): | |
| return f"{prefix_uppercased}_{identifier}" | |
| else: | |
| return f"{prefix_lowercased}_{identifier}" | |
| return identifier | |
| new_text = c_identifier_re.sub(repl, text) | |
| lower_identifier_re = re.compile(r'\b(' + '|'.join(ident for ident in identifiers if ident[0].islower()) + r')\b') | |
| upper_identifier_re = re.compile(r'\b(' + '|'.join(ident for ident in identifiers if ident[0].isupper()) + r')\b') | |
| text = file.read_text(encoding="utf-8") | |
| prefix_uppercased = prefix.upper() | |
| prefix_lowercased = prefix.lower() | |
| new_text = lower_identifier_re.sub(prefix.lower() + r'_\1', | |
| upper_identifier_re.sub(prefix.upper() + r'_\1', text)) |
| Only the `include` and `src` directories from `src_dir` are used to build | ||
| the test driver tree, and their directory structure is preserved. | ||
| Only "*.h" and "*.c" files are copied. Files whose names match any of the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
“file name” either refers to the base name or to the full path (not necessarily absolute), depending on who you ask. Please clarify.
| Only "*.h" and "*.c" files are copied. Files whose names match any of the | |
| Only "*.h" and "*.c" files are copied. Files whose basenames match any of the |
| for ext in (".c", ".h"): | ||
| yield from directory_path.rglob(f"*{ext}") | ||
|
|
||
| def run_ctags(file: Path) -> Set[str]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
“Run ctags” is how the function works, not its purpose. How about something like
| def run_ctags(file: Path) -> Set[str]: | |
| def get_c_identifiers(file: Path) -> Set[str]: |
minosgalanakis
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Functionally works, but there is some work required for the design
|
|
||
| #if defined(MBEDTLS_TEST_LIBTESTDRIVER1) | ||
| #if defined(TF_PSA_CRYPTO_TEST_LIBTESTDRIVER1) | ||
| typedef psa_key_attributes_t libtestdriver1_psa_key_attributes_t; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @gilles-peskine-arm
What the code is effectively trying to achieve is rewrite the include path and include the appropriate header depending on the variant of the libtestdriver.
That logic alongside the typedef could easily reside in a separate header and exposed thought a getter macro e.g LIBTESTDRIVER1_INCLUDE(psa_crypto_hash)
| #if defined(MBEDTLS_TEST_LIBTESTDRIVER1) | ||
| #if defined(TF_PSA_CRYPTO_TEST_LIBTESTDRIVER1) | ||
| typedef psa_key_attributes_t libtestdriver1_psa_key_attributes_t; | ||
| #include "../../libtestdriver1/src/psa_crypto_aead.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1. When people will need to touch the code in the future they likely wont consider looking at the comment history using git log --follow. If its not intuitive it should be a comment
|
|
||
| #if defined(MBEDTLS_TEST_LIBTESTDRIVER1) | ||
| #if !defined(TF_PSA_CRYPTO_TEST_LIBTESTDRIVER1) && \ | ||
| defined(MBEDTLS_TEST_LIBTESTDRIVER1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we end up using a helper header file, this logic and other variants can also be simplified by a macro or symbol defined there.
| mbedtls_test_driver_signature_verify_hooks = MBEDTLS_TEST_DRIVER_SIGNATURE_INIT; | ||
|
|
||
| psa_status_t sign_hash( | ||
| static psa_status_t sign_hash( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that a minor bugfix unreleated to the previous changes? Or required because multiple references of the symbol were included?
| The source directory `src_dir` is expected to have the following structure: | ||
| - an `include` directory | ||
| - an `src` directory | ||
| - the `include` directory contains exactly one subdirectory |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is needed because of the line:
(self.dst_dir / "include" / src_include_dir_name).rename( \
self.test_driver_include_dir)
which will rename the drivers/builtin/include/mbedtls -> drivers/builtin/include/libtestdrive1
| if exclude_files is None: | ||
| exclude_files = set() | ||
|
|
||
| for file in iter_code_files(src_dir): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 Currently the logic required to create this tree is split across two repositories, with tf-psa-crypto/tests/scripts/generate_test_driver.py responsbile to call generator.prefix_identifiers(identifiers). We have historically been having troubles maintaing split logic tools.
The way the data is linked is that build_tree stores data to 'self.dst_dir' which is parsed by get_identifiers_with_prefixes and get_external_identifiers to extract the contents of that to generate_test_driver, allow it to append some identifiers to the list, and call the prefix_identifiers to write them in the file.
I propose we give the TestDriveGeneration information ahead of time, and let it do its thing without having to know how it works from the caller side.
As such a simplistic approach on the api should be:
- create the instance:
generator = test_driver.TestDriverGenerator(dst_dir, args.driver) - Configure it with the infomation it needs. Provide the
EXCLUDE_FILES, extra identifies that need to be added likeMBEDTLS_ECP_LIGHT, set the rename rules e.gPSA_WANT_->MBEDTLS_PSA_ACCEL_. This will need a new.configmethod. - Invoke generator.build_tree or something equivalent that will do all the steps, without exposing the logic to the calling script.
| continue | ||
| dst = self.dst_dir / file.relative_to(src_dir) | ||
| dst.parent.mkdir(parents=True, exist_ok=True) | ||
| shutil.copy2(file, dst) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would keep and return a list of all the files that have been created
Description
Add support for driver dispatch testing in TF-PSA-Crypto
PR checklist