| #!/usr/bin/env perl |
| # |
| # This is a proof-of-concept script to show that the client and server wrappers |
| # can be created by a script. It is not hooked into the build, so is run |
| # manually and the output files are what are to be reviewed. In due course |
| # this will be replaced by a Python script based on the |
| # code_wrapper.psa_wrapper module. |
| # |
| # Copyright The Mbed TLS Contributors |
| # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later |
| # |
| use strict; |
| use Data::Dumper; |
| use File::Basename; |
| use JSON qw(encode_json); |
| |
| my $debug = 0; |
| |
| # Globals (sorry!) |
| my $output_dir = dirname($0); |
| |
| my %functions = get_functions(); |
| my @functions = sort keys %functions; |
| |
| # We don't want these functions (e.g. because they are not implemented, etc) |
| my @skip_functions = ( |
| 'mbedtls_psa_crypto_free', # redefined rather than wrapped |
| 'mbedtls_psa_external_get_random', # not in the default config, uses unsupported type |
| 'mbedtls_psa_get_stats', # uses unsupported type |
| 'mbedtls_psa_platform_get_builtin_key', # not in the default config, uses unsupported type |
| 'psa_get_key_slot_number', # not in the default config, uses unsupported type |
| 'psa_key_derivation_verify_bytes', # not implemented yet |
| 'psa_key_derivation_verify_key', # not implemented yet |
| ); |
| |
| my $skip_functions_re = '\A(' . join('|', @skip_functions). ')\Z'; |
| @functions = grep(!/$skip_functions_re |
| |_pake_ # Skip everything PAKE |
| |_init\Z # constructors |
| /x, @functions); |
| # Restore psa_crypto_init() and put it first. |
| unshift @functions, 'psa_crypto_init'; |
| |
| # get_functions(), called above, returns a data structure for each function |
| # that we need to create client and server stubs for. The functions are |
| # listed from PSA header files. |
| # |
| # In this script, the data for psa_crypto_init() looks like: |
| # |
| # "psa_crypto_init": { |
| # "return": { # Info on return type |
| # "type": "psa_status_t", # Return type |
| # "name": "status", # Name to be used for this in C code |
| # "default": "PSA_ERROR_CORRUPTION_DETECTED" # Default value |
| # }, |
| # "args": [], # void function, so args empty |
| # } |
| # |
| # The data for psa_hash_compute() looks like: |
| # |
| # "psa_hash_compute": { |
| # "return": { # Information on return type |
| # "type": "psa_status_t", |
| # "name": "status", |
| # "default": "PSA_ERROR_CORRUPTION_DETECTED" |
| # }, |
| # "args": [{ |
| # "type": "psa_algorithm_t", # Type of first argument |
| # "ctypename": "psa_algorithm_t ", # C type with trailing spaces |
| # # (so that e.g. `char *` looks ok) |
| # "name": "alg", |
| # "is_output": 0 |
| # }, { |
| # "type": "const buffer", # Specially created |
| # "ctypename": "", # (so no C type) |
| # "name": "input, input_length", # A pair of arguments |
| # "is_output": 0 # const, so not an output argument |
| # }, { |
| # "type": "buffer", # Specially created |
| # "ctypename": "", |
| # "name": "hash, hash_size", |
| # "is_output": 1 # Not const, so output argument |
| # }, { |
| # "type": "size_t", # size_t *hash_length |
| # "ctypename": "size_t ", |
| # "name": "*hash_length", # * comes into the name |
| # "is_output": 1 |
| # } |
| # ], |
| # }, |
| # |
| # It's possible that a production version might not need both type and ctypename; |
| # that was done for convenience and future-proofing during development. |
| |
| write_function_codes("$output_dir/psa_functions_codes.h"); |
| |
| write_client_calls("$output_dir/psa_sim_crypto_client.c"); |
| |
| write_server_implementations("$output_dir/psa_sim_crypto_server.c"); |
| |
| sub write_function_codes |
| { |
| my ($file) = @_; |
| |
| open(my $fh, ">", $file) || die("$0: $file: $!\n"); |
| |
| # NOTE: psa_crypto_init() is written manually |
| |
| print $fh <<EOF; |
| /* THIS FILE WAS AUTO-GENERATED BY psa_sim_generate.pl. DO NOT EDIT!! */ |
| |
| /* |
| * Copyright The Mbed TLS Contributors |
| * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later |
| */ |
| |
| #ifndef _PSA_FUNCTIONS_CODES_H_ |
| #define _PSA_FUNCTIONS_CODES_H_ |
| |
| enum { |
| /* Start here to avoid overlap with PSA_IPC_CONNECT, PSA_IPC_DISCONNECT |
| * and VERSION_REQUEST */ |
| PSA_CRYPTO_INIT = 100, |
| EOF |
| |
| for my $function (@functions) { |
| my $enum = uc($function); |
| if ($enum ne "PSA_CRYPTO_INIT") { |
| print $fh <<EOF; |
| $enum, |
| EOF |
| } |
| } |
| |
| print $fh <<EOF; |
| }; |
| |
| #endif /* _PSA_FUNCTIONS_CODES_H_ */ |
| EOF |
| |
| close($fh); |
| } |
| |
| sub write_client_calls |
| { |
| my ($file) = @_; |
| |
| open(my $fh, ">", $file) || die("$0: $file: $!\n"); |
| |
| print $fh client_calls_header(); |
| |
| for my $function (@functions) { |
| # psa_crypto_init() is hand written to establish connection to server |
| if ($function ne "psa_crypto_init") { |
| my $f = $functions{$function}; |
| output_client($fh, $f, $function); |
| } |
| } |
| |
| close($fh); |
| } |
| |
| sub write_server_implementations |
| { |
| my ($file) = @_; |
| |
| open(my $fh, ">", $file) || die("$0: $file: $!\n"); |
| |
| print $fh server_implementations_header(); |
| |
| print $fh debug_functions() if $debug; |
| |
| for my $function (@functions) { |
| my $f = $functions{$function}; |
| output_server_wrapper($fh, $f, $function); |
| } |
| |
| # Now output a switch statement that calls each of the wrappers |
| |
| print $fh <<EOF; |
| |
| psa_status_t psa_crypto_call(psa_msg_t msg) |
| { |
| int ok = 0; |
| |
| int func = msg.type; |
| |
| /* We only expect a single input buffer, with everything serialised in it */ |
| if (msg.in_size[1] != 0 || msg.in_size[2] != 0 || msg.in_size[3] != 0) { |
| return PSA_ERROR_INVALID_ARGUMENT; |
| } |
| |
| /* We expect exactly 2 output buffers, one for size, the other for data */ |
| if (msg.out_size[0] != sizeof(size_t) || msg.out_size[1] == 0 || |
| msg.out_size[2] != 0 || msg.out_size[3] != 0) { |
| return PSA_ERROR_INVALID_ARGUMENT; |
| } |
| |
| uint8_t *in_params = NULL; |
| size_t in_params_len = 0; |
| uint8_t *out_params = NULL; |
| size_t out_params_len = 0; |
| |
| in_params_len = msg.in_size[0]; |
| in_params = malloc(in_params_len); |
| if (in_params == NULL) { |
| return PSA_ERROR_INSUFFICIENT_MEMORY; |
| } |
| |
| /* Read the bytes from the client */ |
| size_t actual = psa_read(msg.handle, 0, in_params, in_params_len); |
| if (actual != in_params_len) { |
| free(in_params); |
| return PSA_ERROR_CORRUPTION_DETECTED; |
| } |
| |
| switch (func) { |
| EOF |
| |
| for my $function (@functions) { |
| my $f = $functions{$function}; |
| my $enum = uc($function); |
| |
| # Create this call, in a way acceptable to uncustify: |
| # ok = ${function}_wrapper(in_params, in_params_len, |
| # &out_params, &out_params_len); |
| my $first_line = " ok = ${function}_wrapper(in_params, in_params_len,"; |
| my $idx = index($first_line, "("); |
| die("can't find (") if $idx < 0; |
| my $indent = " " x ($idx + 1); |
| |
| print $fh <<EOF; |
| case $enum: |
| $first_line |
| $indent&out_params, &out_params_len); |
| break; |
| EOF |
| } |
| |
| print $fh <<EOF; |
| } |
| |
| free(in_params); |
| |
| if (out_params_len > msg.out_size[1]) { |
| fprintf(stderr, "unable to write %zu bytes into buffer of %zu bytes\\n", |
| out_params_len, msg.out_size[1]); |
| exit(1); |
| } |
| |
| /* Write the exact amount of data we're returning */ |
| psa_write(msg.handle, 0, &out_params_len, sizeof(out_params_len)); |
| |
| /* And write the data itself */ |
| if (out_params_len) { |
| psa_write(msg.handle, 1, out_params, out_params_len); |
| } |
| |
| free(out_params); |
| |
| return ok ? PSA_SUCCESS : PSA_ERROR_GENERIC_ERROR; |
| } |
| EOF |
| |
| # Finally, add psa_crypto_close() |
| |
| print $fh <<EOF; |
| |
| void psa_crypto_close(void) |
| { |
| psa_sim_serialize_reset(); |
| } |
| EOF |
| |
| close($fh); |
| } |
| |
| sub server_implementations_header |
| { |
| return <<'EOF'; |
| /* THIS FILE WAS AUTO-GENERATED BY psa_sim_generate.pl. DO NOT EDIT!! */ |
| |
| /* server implementations */ |
| |
| /* |
| * Copyright The Mbed TLS Contributors |
| * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| |
| #include <psa/crypto.h> |
| |
| #include "psa_functions_codes.h" |
| #include "psa_sim_serialise.h" |
| |
| #include "service.h" |
| |
| #if !defined(MBEDTLS_PSA_CRYPTO_C) |
| #error "Error: MBEDTLS_PSA_CRYPTO_C must be enabled on server build" |
| #endif |
| |
| #if defined(MBEDTLS_TEST_HOOKS) |
| void (*mbedtls_test_hook_error_add)(int, int, const char *, int); |
| #endif |
| EOF |
| } |
| |
| sub client_calls_header |
| { |
| my $code = <<'EOF'; |
| /* THIS FILE WAS AUTO-GENERATED BY psa_sim_generate.pl. DO NOT EDIT!! */ |
| |
| /* client calls */ |
| |
| /* |
| * Copyright The Mbed TLS Contributors |
| * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later |
| */ |
| |
| #include <stdio.h> |
| #include <unistd.h> |
| |
| /* Includes from psasim */ |
| #include <client.h> |
| #include <util.h> |
| #include "psa_manifest/sid.h" |
| #include "psa_functions_codes.h" |
| #include "psa_sim_serialise.h" |
| |
| /* Includes from mbedtls */ |
| #include "mbedtls/version.h" |
| #include "psa/crypto.h" |
| |
| #define CLIENT_PRINT(fmt, ...) \ |
| INFO("Client: " fmt, ##__VA_ARGS__) |
| |
| static psa_handle_t handle = -1; |
| |
| #if defined(MBEDTLS_PSA_CRYPTO_C) |
| #error "Error: MBEDTLS_PSA_CRYPTO_C must be disabled on client build" |
| #endif |
| EOF |
| |
| $code .= debug_functions() if $debug; |
| |
| $code .= <<'EOF'; |
| |
| int psa_crypto_call(int function, |
| uint8_t *in_params, size_t in_params_len, |
| uint8_t **out_params, size_t *out_params_len) |
| { |
| // psa_outvec outvecs[1]; |
| if (handle < 0) { |
| fprintf(stderr, "NOT CONNECTED\n"); |
| exit(1); |
| } |
| |
| psa_invec invec; |
| invec.base = in_params; |
| invec.len = in_params_len; |
| |
| size_t max_receive = 24576; |
| uint8_t *receive = malloc(max_receive); |
| if (receive == NULL) { |
| fprintf(stderr, "FAILED to allocate %u bytes\n", (unsigned) max_receive); |
| exit(1); |
| } |
| |
| size_t actual_received = 0; |
| |
| psa_outvec outvecs[2]; |
| outvecs[0].base = &actual_received; |
| outvecs[0].len = sizeof(actual_received); |
| outvecs[1].base = receive; |
| outvecs[1].len = max_receive; |
| |
| psa_status_t status = psa_call(handle, function, &invec, 1, outvecs, 2); |
| if (status != PSA_SUCCESS) { |
| free(receive); |
| return 0; |
| } |
| |
| *out_params = receive; |
| *out_params_len = actual_received; |
| |
| return 1; // success |
| } |
| |
| psa_status_t psa_crypto_init(void) |
| { |
| const char *mbedtls_version; |
| uint8_t *result = NULL; |
| size_t result_length; |
| psa_status_t status = PSA_ERROR_CORRUPTION_DETECTED; |
| |
| mbedtls_version = mbedtls_version_get_string_full(); |
| CLIENT_PRINT("%s", mbedtls_version); |
| |
| CLIENT_PRINT("My PID: %d", getpid()); |
| |
| CLIENT_PRINT("PSA version: %u", psa_version(PSA_SID_CRYPTO_SID)); |
| handle = psa_connect(PSA_SID_CRYPTO_SID, 1); |
| |
| if (handle < 0) { |
| CLIENT_PRINT("Couldn't connect %d", handle); |
| return PSA_ERROR_COMMUNICATION_FAILURE; |
| } |
| |
| int ok = psa_crypto_call(PSA_CRYPTO_INIT, NULL, 0, &result, &result_length); |
| CLIENT_PRINT("PSA_CRYPTO_INIT returned: %d", ok); |
| |
| if (!ok) { |
| goto fail; |
| } |
| |
| uint8_t *rpos = result; |
| size_t rremain = result_length; |
| |
| ok = psasim_deserialise_begin(&rpos, &rremain); |
| if (!ok) { |
| goto fail; |
| } |
| |
| ok = psasim_deserialise_psa_status_t(&rpos, &rremain, &status); |
| if (!ok) { |
| goto fail; |
| } |
| |
| fail: |
| free(result); |
| |
| return status; |
| } |
| |
| void mbedtls_psa_crypto_free(void) |
| { |
| /* Do not try to close a connection that was never started.*/ |
| if (handle == -1) { |
| return; |
| } |
| |
| CLIENT_PRINT("Closing handle"); |
| psa_close(handle); |
| handle = -1; |
| } |
| EOF |
| } |
| |
| sub debug_functions |
| { |
| return <<EOF; |
| |
| static inline char hex_digit(char nibble) { |
| return (nibble < 10) ? (nibble + '0') : (nibble + 'a' - 10); |
| } |
| |
| int hex_byte(char *p, uint8_t b) |
| { |
| p[0] = hex_digit(b >> 4); |
| p[1] = hex_digit(b & 0x0F); |
| |
| return 2; |
| } |
| |
| int hex_uint16(char *p, uint16_t b) |
| { |
| hex_byte(p, b >> 8); |
| hex_byte(p + 2, b & 0xFF); |
| |
| return 4; |
| } |
| |
| char human_char(uint8_t c) |
| { |
| return (c >= ' ' && c <= '~') ? (char)c : '.'; |
| } |
| |
| void dump_buffer(const uint8_t *buffer, size_t len) |
| { |
| char line[80]; |
| |
| const uint8_t *p = buffer; |
| |
| size_t max = (len > 0xFFFF) ? 0xFFFF : len; |
| |
| for (size_t i = 0; i < max; i += 16) { |
| |
| char *q = line; |
| |
| q += hex_uint16(q, (uint16_t)i); |
| *q++ = ' '; |
| *q++ = ' '; |
| |
| size_t ll = (i + 16 > max) ? (max % 16) : 16; |
| |
| size_t j; |
| for (j = 0; j < ll; j++) { |
| q += hex_byte(q, p[i + j]); |
| *q++ = ' '; |
| } |
| |
| while (j++ < 16) { |
| *q++ = ' '; |
| *q++ = ' '; |
| *q++ = ' '; |
| } |
| |
| *q++ = ' '; |
| |
| for (j = 0; j < ll; j++) { |
| *q++ = human_char(p[i + j]); |
| } |
| |
| *q = '\\0'; |
| |
| printf("%s\\n", line); |
| } |
| } |
| |
| void hex_dump(uint8_t *p, size_t n) |
| { |
| for (size_t i = 0; i < n; i++) { |
| printf("0x%02X ", p[i]); |
| } |
| printf("\\n"); |
| } |
| EOF |
| } |
| |
| sub output_server_wrapper |
| { |
| my ($fh, $f, $name) = @_; |
| |
| my $ret_type = $f->{return}->{type}; |
| my $ret_name = $f->{return}->{name}; |
| my $ret_default = $f->{return}->{default}; |
| |
| my @buffers = (); # We need to free() these on exit |
| |
| print $fh <<EOF; |
| |
| // Returns 1 for success, 0 for failure |
| int ${name}_wrapper( |
| uint8_t *in_params, size_t in_params_len, |
| uint8_t **out_params, size_t *out_params_len) |
| { |
| EOF |
| |
| print $fh <<EOF unless $ret_type eq "void"; |
| $ret_type $ret_name = $ret_default; |
| EOF |
| # Output the variables we will need when we call the target function |
| |
| my $args = $f->{args}; |
| |
| for my $i (0 .. $#$args) { |
| my $arg = $args->[$i]; |
| my $argtype = $arg->{type}; # e.g. int, psa_algorithm_t, or "buffer" |
| my $argname = $arg->{name}; |
| $argtype =~ s/^const //; |
| |
| if ($argtype =~ /^(const )?buffer$/) { |
| my ($n1, $n2) = split(/,\s*/, $argname); |
| print $fh <<EOF; |
| uint8_t *$n1 = NULL; |
| size_t $n2; |
| EOF |
| push(@buffers, $n1); # Add to the list to be free()d at end |
| } else { |
| $argname =~ s/^\*//; # Remove any leading * |
| my $pointer = ($argtype =~ /^psa_\w+_operation_t/) ? "*" : ""; |
| print $fh <<EOF; |
| $argtype $pointer$argname; |
| EOF |
| } |
| } |
| |
| print $fh "\n"; |
| |
| if ($#$args >= 0) { # If we have any args (>= 0) |
| print $fh <<EOF; |
| uint8_t *pos = in_params; |
| size_t remaining = in_params_len; |
| EOF |
| } |
| |
| print $fh <<EOF; |
| uint8_t *result = NULL; |
| int ok; |
| EOF |
| |
| print $fh <<EOF if $debug; |
| |
| printf("$name: server\\n"); |
| EOF |
| if ($#$args >= 0) { # If we have any args (>= 0) |
| print $fh <<EOF; |
| |
| ok = psasim_deserialise_begin(&pos, &remaining); |
| if (!ok) { |
| goto fail; |
| } |
| EOF |
| } |
| |
| for my $i (0 .. $#$args) { |
| my $arg = $args->[$i]; |
| my $argtype = $arg->{type}; # e.g. int, psa_algorithm_t, or "buffer" |
| my $argname = $arg->{name}; |
| my $sep = ($i == $#$args) ? ";" : " +"; |
| $argtype =~ s/^const //; |
| |
| if ($argtype =~ /^(const )?buffer$/) { |
| my ($n1, $n2) = split(/,\s*/, $argname); |
| print $fh <<EOF; |
| |
| ok = psasim_deserialise_${argtype}( |
| &pos, &remaining, |
| &$n1, &$n2); |
| if (!ok) { |
| goto fail; |
| } |
| EOF |
| } else { |
| $argname =~ s/^\*//; # Remove any leading * |
| my $server_specific = ($argtype =~ /^psa_\w+_operation_t/) ? "server_" : ""; |
| print $fh <<EOF; |
| |
| ok = psasim_${server_specific}deserialise_${argtype}( |
| &pos, &remaining, |
| &$argname); |
| if (!ok) { |
| goto fail; |
| } |
| EOF |
| } |
| } |
| |
| print $fh <<EOF; |
| |
| // Now we call the actual target function |
| EOF |
| output_call($fh, $f, $name, 1); |
| |
| my @outputs = grep($_->{is_output}, @$args); |
| |
| my $sep1 = (($ret_type eq "void") and ($#outputs < 0)) ? ";" : " +"; |
| |
| print $fh <<EOF; |
| |
| // NOTE: Should really check there is no overflow as we go along. |
| size_t result_size = |
| psasim_serialise_begin_needs()$sep1 |
| EOF |
| |
| if ($ret_type ne "void") { |
| my $sep = ($#outputs < 0) ? ";" : " +"; |
| print $fh <<EOF; |
| psasim_serialise_${ret_type}_needs($ret_name)$sep |
| EOF |
| } |
| |
| for my $i (0 .. $#outputs) { |
| my $arg = $outputs[$i]; |
| die("$i: this should have been filtered out by grep") unless $arg->{is_output}; |
| my $argtype = $arg->{type}; # e.g. int, psa_algorithm_t, or "buffer" |
| my $argname = $arg->{name}; |
| my $sep = ($i == $#outputs) ? ";" : " +"; |
| $argtype =~ s/^const //; |
| $argname =~ s/^\*//; # Remove any leading * |
| my $server_specific = ($argtype =~ /^psa_\w+_operation_t/) ? "server_" : ""; |
| |
| print $fh <<EOF; |
| psasim_${server_specific}serialise_${argtype}_needs($argname)$sep |
| EOF |
| } |
| |
| print $fh <<EOF; |
| |
| result = malloc(result_size); |
| if (result == NULL) { |
| goto fail; |
| } |
| |
| uint8_t *rpos = result; |
| size_t rremain = result_size; |
| |
| ok = psasim_serialise_begin(&rpos, &rremain); |
| if (!ok) { |
| goto fail; |
| } |
| EOF |
| |
| if ($ret_type ne "void") { |
| print $fh <<EOF; |
| |
| ok = psasim_serialise_${ret_type}( |
| &rpos, &rremain, |
| $ret_name); |
| if (!ok) { |
| goto fail; |
| } |
| EOF |
| } |
| |
| my @outputs = grep($_->{is_output}, @$args); |
| |
| for my $i (0 .. $#outputs) { |
| my $arg = $outputs[$i]; |
| die("$i: this should have been filtered out by grep") unless $arg->{is_output}; |
| my $argtype = $arg->{type}; # e.g. int, psa_algorithm_t, or "buffer" |
| my $argname = $arg->{name}; |
| my $sep = ($i == $#outputs) ? ";" : " +"; |
| $argtype =~ s/^const //; |
| |
| if ($argtype eq "buffer") { |
| print $fh <<EOF; |
| |
| ok = psasim_serialise_buffer( |
| &rpos, &rremain, |
| $argname); |
| if (!ok) { |
| goto fail; |
| } |
| EOF |
| } else { |
| if ($argname =~ /^\*/) { |
| $argname =~ s/^\*//; # since it's already a pointer |
| } else { |
| die("$0: $argname: HOW TO OUTPUT?\n"); |
| } |
| |
| my $server_specific = ($argtype =~ /^psa_\w+_operation_t/) ? "server_" : ""; |
| |
| my $completed = ""; # Only needed on server serialise calls |
| if (length($server_specific)) { |
| # On server serialisation, which is only for operation types, |
| # we need to mark the operation as completed (variously called |
| # terminated or inactive in psa/crypto.h) on certain calls. |
| $completed = ($name =~ /_(abort|finish|hash_verify)$/) ? ", 1" : ", 0"; |
| } |
| |
| print $fh <<EOF; |
| |
| ok = psasim_${server_specific}serialise_${argtype}( |
| &rpos, &rremain, |
| $argname$completed); |
| if (!ok) { |
| goto fail; |
| } |
| EOF |
| } |
| } |
| |
| my $free_buffers = join("", map { " free($_);\n" } @buffers); |
| $free_buffers = "\n" . $free_buffers if length($free_buffers); |
| |
| print $fh <<EOF; |
| |
| *out_params = result; |
| *out_params_len = result_size; |
| $free_buffers |
| return 1; // success |
| |
| fail: |
| free(result); |
| $free_buffers |
| return 0; // This shouldn't happen! |
| } |
| EOF |
| } |
| |
| sub output_client |
| { |
| my ($fh, $f, $name) = @_; |
| |
| print $fh "\n"; |
| |
| output_definition_begin($fh, $f, $name); |
| |
| my $ret_type = $f->{return}->{type}; |
| my $ret_name = $f->{return}->{name}; |
| my $ret_default = $f->{return}->{default}; |
| |
| print $fh <<EOF; |
| { |
| uint8_t *ser_params = NULL; |
| uint8_t *ser_result = NULL; |
| size_t result_length; |
| EOF |
| print $fh <<EOF unless $ret_type eq "void"; |
| $ret_type $ret_name = $ret_default; |
| EOF |
| |
| print $fh <<EOF if $debug; |
| |
| printf("$name: client\\n"); |
| EOF |
| |
| print $fh <<EOF; |
| |
| size_t needed = |
| psasim_serialise_begin_needs() + |
| EOF |
| |
| my $args = $f->{args}; |
| |
| for my $i (0 .. $#$args) { |
| my $arg = $args->[$i]; |
| my $argtype = $arg->{type}; # e.g. int, psa_algorithm_t, or "buffer" |
| my $argname = $arg->{name}; |
| my $sep = ($i == $#$args) ? ";" : " +"; |
| $argtype =~ s/^const //; |
| |
| print $fh <<EOF; |
| psasim_serialise_${argtype}_needs($argname)$sep |
| EOF |
| } |
| |
| print $fh <<EOF if $#$args < 0; |
| 0; |
| EOF |
| |
| print $fh <<EOF; |
| |
| ser_params = malloc(needed); |
| if (ser_params == NULL) { |
| EOF |
| |
| if ($ret_type eq "psa_status_t") { |
| print $fh <<EOF if $; |
| $ret_name = PSA_ERROR_INSUFFICIENT_MEMORY; |
| EOF |
| } elsif ($ret_type eq "uint32_t") { |
| print $fh <<EOF if $; |
| $ret_name = 0; |
| EOF |
| } |
| |
| print $fh <<EOF; |
| goto fail; |
| } |
| |
| uint8_t *pos = ser_params; |
| size_t remaining = needed; |
| int ok; |
| ok = psasim_serialise_begin(&pos, &remaining); |
| if (!ok) { |
| goto fail; |
| } |
| EOF |
| |
| for my $i (0 .. $#$args) { |
| my $arg = $args->[$i]; |
| my $argtype = $arg->{type}; # e.g. int, psa_algorithm_t, or "buffer" |
| my $argname = $arg->{name}; |
| my $sep = ($i == $#$args) ? ";" : " +"; |
| $argtype =~ s/^const //; |
| |
| print $fh <<EOF; |
| ok = psasim_serialise_${argtype}( |
| &pos, &remaining, |
| $argname); |
| if (!ok) { |
| goto fail; |
| } |
| EOF |
| } |
| |
| print $fh <<EOF if $debug; |
| |
| printf("client sending %d:\\n", (int)(pos - ser_params)); |
| dump_buffer(ser_params, (size_t)(pos - ser_params)); |
| EOF |
| |
| my $enum = uc($name); |
| |
| print $fh <<EOF; |
| |
| ok = psa_crypto_call($enum, |
| ser_params, (size_t) (pos - ser_params), &ser_result, &result_length); |
| if (!ok) { |
| printf("$enum server call failed\\n"); |
| goto fail; |
| } |
| EOF |
| |
| print $fh <<EOF if $debug; |
| |
| printf("client receiving %d:\\n", (int)result_length); |
| dump_buffer(ser_result, result_length); |
| EOF |
| |
| print $fh <<EOF; |
| |
| uint8_t *rpos = ser_result; |
| size_t rremain = result_length; |
| |
| ok = psasim_deserialise_begin(&rpos, &rremain); |
| if (!ok) { |
| goto fail; |
| } |
| EOF |
| |
| print $fh <<EOF if $ret_type ne "void"; |
| |
| ok = psasim_deserialise_$ret_type( |
| &rpos, &rremain, |
| &$ret_name); |
| if (!ok) { |
| goto fail; |
| } |
| EOF |
| |
| my @outputs = grep($_->{is_output}, @$args); |
| |
| for my $i (0 .. $#outputs) { |
| my $arg = $outputs[$i]; |
| die("$i: this should have been filtered out by grep") unless $arg->{is_output}; |
| my $argtype = $arg->{type}; # e.g. int, psa_algorithm_t, or "buffer" |
| my $argname = $arg->{name}; |
| my $sep = ($i == $#outputs) ? ";" : " +"; |
| $argtype =~ s/^const //; |
| |
| if ($argtype eq "buffer") { |
| print $fh <<EOF; |
| |
| ok = psasim_deserialise_return_buffer( |
| &rpos, &rremain, |
| $argname); |
| if (!ok) { |
| goto fail; |
| } |
| EOF |
| } else { |
| if ($argname =~ /^\*/) { |
| $argname =~ s/^\*//; # since it's already a pointer |
| } else { |
| die("$0: $argname: HOW TO OUTPUT?\n"); |
| } |
| |
| print $fh <<EOF; |
| |
| ok = psasim_deserialise_${argtype}( |
| &rpos, &rremain, |
| $argname); |
| if (!ok) { |
| goto fail; |
| } |
| EOF |
| } |
| } |
| print $fh <<EOF; |
| |
| fail: |
| free(ser_params); |
| free(ser_result); |
| EOF |
| |
| print $fh <<EOF if $ret_type ne "void"; |
| |
| return $ret_name; |
| EOF |
| |
| print $fh <<EOF; |
| } |
| EOF |
| } |
| |
| sub output_declaration |
| { |
| my ($f, $name) = @_; |
| |
| output_signature($f, $name, "declaration"); |
| } |
| |
| sub output_definition_begin |
| { |
| my ($fh, $f, $name) = @_; |
| |
| output_signature($fh, $f, $name, "definition"); |
| } |
| |
| sub output_call |
| { |
| my ($fh, $f, $name, $is_server) = @_; |
| |
| my $ret_type = $f->{return}->{type}; |
| my $ret_name = $f->{return}->{name}; |
| my $args = $f->{args}; |
| |
| if ($ret_type eq "void") { |
| print $fh "\n $name(\n"; |
| } else { |
| print $fh "\n $ret_name = $name(\n"; |
| } |
| |
| print $fh " );\n" if $#$args < 0; # If no arguments, empty arg list |
| |
| for my $i (0 .. $#$args) { |
| my $arg = $args->[$i]; |
| my $argtype = $arg->{type}; # e.g. int, psa_algorithm_t, or "buffer" |
| my $argname = $arg->{name}; |
| |
| if ($argtype =~ /^(const )?buffer$/) { |
| my ($n1, $n2) = split(/,\s*/, $argname); |
| print $fh " $n1, $n2"; |
| } else { |
| $argname =~ s/^\*/\&/; # Replace leading * with & |
| if ($is_server && $argtype =~ /^psa_\w+_operation_t/) { |
| $argname =~ s/^\&//; # Actually, for psa_XXX_operation_t, don't do this on the server side |
| } |
| print $fh " $argname"; |
| } |
| my $sep = ($i == $#$args) ? "\n );" : ","; |
| print $fh "$sep\n"; |
| } |
| } |
| |
| sub output_signature |
| { |
| my ($fh, $f, $name, $what) = @_; |
| |
| my $ret_type = $f->{return}->{type}; |
| my $args = $f->{args}; |
| |
| my $final_sep = ($what eq "declaration") ? "\n);" : "\n )"; |
| |
| print $fh "\n$ret_type $name(\n"; |
| |
| print $fh " void\n )\n" if $#$args < 0; # No arguments |
| |
| for my $i (0 .. $#$args) { |
| my $arg = $args->[$i]; |
| my $argtype = $arg->{type}; # e.g. int, psa_algorithm_t, or "buffer" |
| my $ctypename = $arg->{ctypename}; # e.g. "int ", "char *"; empty for buffer |
| my $argname = $arg->{name}; |
| |
| if ($argtype =~ /^(const )?buffer$/) { |
| my $const = length($1) ? "const " : ""; |
| my ($n1, $n2) = split(/,/, $argname); |
| print $fh " ${const}uint8_t *$n1, size_t $n2"; |
| } else { |
| print $fh " $ctypename$argname"; |
| } |
| my $sep = ($i == $#$args) ? $final_sep : ","; |
| print $fh "$sep\n"; |
| } |
| } |
| |
| sub get_functions |
| { |
| my $header_dir = 'tf-psa-crypto/include'; |
| my $src = ""; |
| for my $header_file ('psa/crypto.h', 'psa/crypto_extra.h') { |
| local *HEADER; |
| open HEADER, '<', "$header_dir/$header_file" |
| or die "$header_dir/$header_file: $!"; |
| while (<HEADER>) { |
| chomp; |
| s/\/\/.*//; |
| s/\s+^//; |
| s/\s+/ /g; |
| $_ .= "\n"; |
| $src .= $_; |
| } |
| close HEADER; |
| } |
| |
| $src =~ s/\/\*.*?\*\///gs; |
| |
| my @src = split(/\n+/, $src); |
| |
| my @rebuild = (); |
| my %funcs = (); |
| for (my $i = 0; $i <= $#src; $i++) { |
| my $line = $src[$i]; |
| if ($line =~ /^(static(?:\s+inline)?\s+)? |
| ((?:(?:enum|struct|union)\s+)?\w+\s*\**\s*)\s+ |
| ((?:mbedtls|psa)_\w*)\(/x) { |
| # begin function declaration |
| #print "have one $line\n"; |
| while ($line !~ /;/) { |
| $line .= $src[$i + 1]; |
| $i++; |
| } |
| if ($line =~ /^static/) { |
| # IGNORE static inline functions: they're local. |
| next; |
| } |
| $line =~ s/\s+/ /g; |
| if ($line =~ /(\w+)\s+\b(\w+)\s*\(\s*(.*\S)\s*\)\s*[;{]/s) { |
| my ($ret_type, $func, $args) = ($1, $2, $3); |
| |
| my $copy = $line; |
| $copy =~ s/{$//; |
| my $f = { |
| "orig" => $copy, |
| }; |
| |
| my @args = split(/\s*,\s*/, $args); |
| |
| my $ret_name = ""; |
| $ret_name = "status" if $ret_type eq "psa_status_t"; |
| $ret_name = "value" if $ret_type eq "uint32_t"; |
| $ret_name = "value" if $ret_type eq "int"; |
| $ret_name = "(void)" if $ret_type eq "void"; |
| die("ret_name for $ret_type?") unless length($ret_name); |
| my $ret_default = ""; |
| $ret_default = "PSA_ERROR_CORRUPTION_DETECTED" if $ret_type eq "psa_status_t"; |
| $ret_default = "0" if $ret_type eq "uint32_t"; |
| $ret_default = "0" if $ret_type eq "int"; |
| $ret_default = "(void)" if $ret_type eq "void"; |
| die("ret_default for $ret_type?") unless length($ret_default); |
| |
| #print "FUNC $func RET_NAME $ret_name RET_TYPE $ret_type ARGS (", join("; ", @args), ")\n"; |
| |
| $f->{return} = { |
| "type" => $ret_type, |
| "default" => $ret_default, |
| "name" => $ret_name, |
| }; |
| $f->{args} = []; |
| # psa_algorithm_t alg; const uint8_t *input; size_t input_length; uint8_t *hash; size_t hash_size; size_t *hash_length |
| for (my $i = 0; $i <= $#args; $i++) { |
| my $arg = $args[$i]; |
| # "type" => "psa_algorithm_t", |
| # "ctypename" => "psa_algorithm_t ", |
| # "name" => "alg", |
| # "is_output" => 0, |
| my ($type, $ctype, $name, $is_output); |
| if ($arg =~ /^(\w+)\s+(\w+)$/) { # e.g. psa_algorithm_t alg |
| ($type, $name) = ($1, $2); |
| $ctype = $type . " "; |
| $is_output = 0; |
| } elsif ($arg =~ /^((const)\s+)?uint8_t\s*\*\s*(\w+)$/) { |
| $type = "buffer"; |
| $is_output = (length($1) == 0) ? 1 : 0; |
| $type = "const buffer" if !$is_output; |
| $ctype = ""; |
| $name = $3; |
| #print("$arg: $name: might be a buffer?\n"); |
| die("$arg: not a buffer 1!\n") if $i == $#args; |
| my $next = $args[$i + 1]; |
| if ($func eq "psa_key_derivation_verify_bytes" && |
| $arg eq "const uint8_t *expected_output" && |
| $next eq "size_t output_length") { |
| $next = "size_t expected_output_length"; # doesn't follow naming convention, so override |
| } |
| die("$arg: not a buffer 2!\n") if $next !~ /^size_t\s+(${name}_\w+)$/; |
| $i++; # We're using the next param here |
| my $nname = $1; |
| $name .= ", " . $nname; |
| } elsif ($arg =~ /^((const)\s+)?(\w+)\s*\*(\w+)$/) { |
| ($type, $name) = ($3, "*" . $4); |
| $ctype = $1 . $type . " "; |
| $is_output = (length($1) == 0) ? 1 : 0; |
| } elsif ($arg eq "void") { |
| # we'll just ignore this one |
| } else { |
| die("ARG HELP $arg\n"); |
| } |
| #print "$arg => <$type><$ctype><$name><$is_output>\n"; |
| if ($arg ne "void") { |
| push(@{$f->{args}}, { |
| "type" => $type, |
| "ctypename" => $ctype, |
| "name" => $name, |
| "is_output" => $is_output, |
| }); |
| } |
| } |
| $funcs{$func} = $f; |
| } else { |
| die("FAILED"); |
| } |
| push(@rebuild, $line); |
| } elsif ($line =~ /^#/i) { |
| # IGNORE directive |
| while ($line =~ /\\$/) { |
| $i++; |
| $line = $src[$i]; |
| } |
| } elsif ($line =~ /^(?:typedef +)?(enum|struct|union)[^;]*$/) { |
| # IGNORE compound type definition |
| while ($line !~ /^\}/) { |
| $i++; |
| $line = $src[$i]; |
| } |
| } elsif ($line =~ /^typedef /i) { |
| # IGNORE type definition |
| } elsif ($line =~ / = .*;$/) { |
| # IGNORE assignment in inline function definition |
| } else { |
| if ($line =~ /psa_/) { |
| print "NOT PARSED: $line\n"; |
| } |
| push(@rebuild, $line); |
| } |
| } |
| |
| #print ::Dumper(\%funcs); |
| #exit; |
| |
| return %funcs; |
| } |