blob: 0f4c86f817411a6773310cafee4750df0a66f991 [file] [log] [blame]
#!/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;
}