From a6425067bf8c2e2f9e46e31cc1c6ec395f17bba6 Mon Sep 17 00:00:00 2001 From: Carl Niklas Rydberg Date: Sat, 20 Dec 2025 14:27:26 +0100 Subject: [PATCH] Add PEL program DAG validation and kernel op execution --- CMakeLists.txt | 11 + include/amduat/pel/opreg_kernel.h | 66 ++ include/amduat/pel/opreg_kernel_params.h | 46 ++ include/amduat/pel/program_dag.h | 14 + include/amduat/pel/program_dag_desc.h | 23 + src/pel_stack/opreg/kernel.c | 436 +++++++++++++ src/pel_stack/opreg/kernel_params.c | 132 ++++ src/pel_stack/program_dag/program_dag.c | 642 +++++++++++++++++++ src/pel_stack/program_dag/program_dag_desc.c | 15 + tests/pel/test_pel_program_dag_exec.c | 296 +++++++++ 10 files changed, 1681 insertions(+) create mode 100644 tests/pel/test_pel_program_dag_exec.c diff --git a/CMakeLists.txt b/CMakeLists.txt index fa081b8..de712c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -180,3 +180,14 @@ target_link_libraries(amduat_test_pel1_result PRIVATE amduat_enc amduat_hash_asl1 amduat_asl amduat_util ) add_test(NAME pel1_result COMMAND amduat_test_pel1_result) + +add_executable(amduat_test_pel_program_dag_exec + tests/pel/test_pel_program_dag_exec.c) +target_include_directories(amduat_test_pel_program_dag_exec + PRIVATE ${AMDUAT_INTERNAL_DIR} + PRIVATE ${AMDUAT_INCLUDE_DIR} +) +target_link_libraries(amduat_test_pel_program_dag_exec + PRIVATE amduat_pel +) +add_test(NAME pel_program_dag_exec COMMAND amduat_test_pel_program_dag_exec) diff --git a/include/amduat/pel/opreg_kernel.h b/include/amduat/pel/opreg_kernel.h index e69de29..c1374af 100644 --- a/include/amduat/pel/opreg_kernel.h +++ b/include/amduat/pel/opreg_kernel.h @@ -0,0 +1,66 @@ +#ifndef AMDUAT_PEL_OPREG_KERNEL_H +#define AMDUAT_PEL_OPREG_KERNEL_H + +#include "amduat/asl/core.h" + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define AMDUAT_PEL_KERNEL_OP_CONCAT_NAME "pel.bytes.concat" +#define AMDUAT_PEL_KERNEL_OP_SLICE_NAME "pel.bytes.slice" +#define AMDUAT_PEL_KERNEL_OP_CONST_NAME "pel.bytes.const" +#define AMDUAT_PEL_KERNEL_OP_HASH_ASL1_NAME "pel.bytes.hash.asl1" + +enum { + AMDUAT_PEL_KERNEL_OP_CODE_CONCAT = 0x0001u, + AMDUAT_PEL_KERNEL_OP_CODE_SLICE = 0x0002u, + AMDUAT_PEL_KERNEL_OP_CODE_CONST = 0x0003u, + AMDUAT_PEL_KERNEL_OP_CODE_HASH_ASL1 = 0x0004u +}; + +enum { + AMDUAT_PEL_KERNEL_STATUS_CONCAT_TYPE_TAG_MISMATCH = 0x00010001u, + AMDUAT_PEL_KERNEL_STATUS_SLICE_RANGE_OUT_OF_BOUNDS = 0x00020001u +}; + +typedef enum { + AMDUAT_PEL_KERNEL_OP_CONCAT = 1, + AMDUAT_PEL_KERNEL_OP_SLICE = 2, + AMDUAT_PEL_KERNEL_OP_CONST = 3, + AMDUAT_PEL_KERNEL_OP_HASH_ASL1 = 4 +} amduat_pel_kernel_op_kind_t; + +typedef struct { + amduat_pel_kernel_op_kind_t kind; + uint32_t kernel_op_code; + size_t min_inputs; + size_t max_inputs; + size_t outputs_len; +} amduat_pel_kernel_op_desc_t; + +struct amduat_pel_kernel_params; +typedef struct amduat_pel_kernel_params amduat_pel_kernel_params_t; + +const amduat_pel_kernel_op_desc_t *amduat_pel_kernel_op_lookup( + amduat_octets_t name, + uint32_t version); + +bool amduat_pel_kernel_op_eval( + const amduat_pel_kernel_op_desc_t *desc, + const amduat_artifact_t *inputs, + size_t inputs_len, + const amduat_pel_kernel_params_t *params, + amduat_artifact_t **out_outputs, + size_t *out_outputs_len, + uint32_t *out_status_code); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* AMDUAT_PEL_OPREG_KERNEL_H */ diff --git a/include/amduat/pel/opreg_kernel_params.h b/include/amduat/pel/opreg_kernel_params.h index e69de29..4c29879 100644 --- a/include/amduat/pel/opreg_kernel_params.h +++ b/include/amduat/pel/opreg_kernel_params.h @@ -0,0 +1,46 @@ +#ifndef AMDUAT_PEL_OPREG_KERNEL_PARAMS_H +#define AMDUAT_PEL_OPREG_KERNEL_PARAMS_H + +#include "amduat/pel/opreg_kernel.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + uint64_t offset; + uint64_t length; +} amduat_pel_kernel_slice_params_t; + +typedef struct { + amduat_octets_t bytes; + bool has_type_tag; + uint32_t tag_id; +} amduat_pel_kernel_const_params_t; + +typedef struct { + amduat_hash_id_t hash_id; +} amduat_pel_kernel_hash_params_t; + +struct amduat_pel_kernel_params { + amduat_pel_kernel_op_kind_t kind; + union { + amduat_pel_kernel_slice_params_t slice; + amduat_pel_kernel_const_params_t konst; + amduat_pel_kernel_hash_params_t hash; + } value; +}; + +bool amduat_pel_kernel_params_decode( + const amduat_pel_kernel_op_desc_t *desc, + amduat_octets_t params_bytes, + amduat_pel_kernel_params_t *out_params); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* AMDUAT_PEL_OPREG_KERNEL_PARAMS_H */ diff --git a/include/amduat/pel/program_dag.h b/include/amduat/pel/program_dag.h index a7177b9..5373751 100644 --- a/include/amduat/pel/program_dag.h +++ b/include/amduat/pel/program_dag.h @@ -2,6 +2,7 @@ #define AMDUAT_PEL_PROGRAM_DAG_H #include "amduat/asl/core.h" +#include "amduat/pel/core.h" #include #include @@ -60,6 +61,19 @@ typedef struct { size_t roots_len; } amduat_pel_program_t; +bool amduat_pel_program_dag_validate(const amduat_pel_program_t *program); + +bool amduat_pel_program_dag_exec( + const amduat_pel_program_t *program, + const amduat_artifact_t *inputs, + size_t inputs_len, + amduat_artifact_t **out_outputs, + size_t *out_outputs_len, + amduat_pel_execution_result_value_t *out_result); + +void amduat_pel_program_dag_free_outputs(amduat_artifact_t *outputs, + size_t outputs_len); + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/include/amduat/pel/program_dag_desc.h b/include/amduat/pel/program_dag_desc.h index e69de29..3c5b1d1 100644 --- a/include/amduat/pel/program_dag_desc.h +++ b/include/amduat/pel/program_dag_desc.h @@ -0,0 +1,23 @@ +#ifndef AMDUAT_PEL_PROGRAM_DAG_DESC_H +#define AMDUAT_PEL_PROGRAM_DAG_DESC_H + +#include "amduat/asl/core.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + AMDUAT_PEL_TYPE_TAG_SCHEME_DESC_1 = 0x00000100u, + AMDUAT_PEL_TYPE_TAG_PROGRAM_DAG_1 = 0x00000101u +}; + +amduat_reference_t amduat_pel_program_dag_scheme_ref(void); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* AMDUAT_PEL_PROGRAM_DAG_DESC_H */ diff --git a/src/pel_stack/opreg/kernel.c b/src/pel_stack/opreg/kernel.c index e69de29..2190e29 100644 --- a/src/pel_stack/opreg/kernel.c +++ b/src/pel_stack/opreg/kernel.c @@ -0,0 +1,436 @@ +#include "amduat/pel/opreg_kernel.h" +#include "amduat/pel/opreg_kernel_params.h" +#include "amduat/hash/asl1.h" + +#include +#include +#include +#include +#include + +enum { AMDUAT_PEL_KERNEL_STATUS_INTERNAL = 1 }; + +static bool amduat_name_eq(amduat_octets_t name, const char *literal) { + size_t len = strlen(literal); + if (name.len != len) { + return false; + } + if (len == 0) { + return true; + } + return name.data != NULL && + memcmp(name.data, literal, len) == 0; +} + +static const amduat_pel_kernel_op_desc_t k_concat_desc = { + AMDUAT_PEL_KERNEL_OP_CONCAT, + AMDUAT_PEL_KERNEL_OP_CODE_CONCAT, + 1, + SIZE_MAX, + 1 +}; + +static const amduat_pel_kernel_op_desc_t k_slice_desc = { + AMDUAT_PEL_KERNEL_OP_SLICE, + AMDUAT_PEL_KERNEL_OP_CODE_SLICE, + 1, + 1, + 1 +}; + +static const amduat_pel_kernel_op_desc_t k_const_desc = { + AMDUAT_PEL_KERNEL_OP_CONST, + AMDUAT_PEL_KERNEL_OP_CODE_CONST, + 0, + 0, + 1 +}; + +static const amduat_pel_kernel_op_desc_t k_hash_desc = { + AMDUAT_PEL_KERNEL_OP_HASH_ASL1, + AMDUAT_PEL_KERNEL_OP_CODE_HASH_ASL1, + 1, + 1, + 1 +}; + +const amduat_pel_kernel_op_desc_t *amduat_pel_kernel_op_lookup( + amduat_octets_t name, + uint32_t version) { + if (version != 1) { + return NULL; + } + if (amduat_name_eq(name, AMDUAT_PEL_KERNEL_OP_CONCAT_NAME)) { + return &k_concat_desc; + } + if (amduat_name_eq(name, AMDUAT_PEL_KERNEL_OP_SLICE_NAME)) { + return &k_slice_desc; + } + if (amduat_name_eq(name, AMDUAT_PEL_KERNEL_OP_CONST_NAME)) { + return &k_const_desc; + } + if (amduat_name_eq(name, AMDUAT_PEL_KERNEL_OP_HASH_ASL1_NAME)) { + return &k_hash_desc; + } + return NULL; +} + +static bool amduat_type_tags_match(const amduat_artifact_t *a, + const amduat_artifact_t *b) { + if (a->has_type_tag != b->has_type_tag) { + return false; + } + if (!a->has_type_tag) { + return true; + } + return a->type_tag.tag_id == b->type_tag.tag_id; +} + +static bool amduat_alloc_outputs(size_t count, + amduat_artifact_t **out_outputs) { + amduat_artifact_t *outputs; + + if (out_outputs == NULL) { + return false; + } + outputs = (amduat_artifact_t *)calloc(count, sizeof(*outputs)); + if (outputs == NULL) { + return false; + } + *out_outputs = outputs; + return true; +} + +static bool amduat_kernel_concat(const amduat_artifact_t *inputs, + size_t inputs_len, + amduat_artifact_t **out_outputs, + size_t *out_outputs_len, + uint32_t *out_status_code) { + size_t i; + size_t total_len = 0; + uint8_t *buffer; + + if (inputs == NULL) { + if (out_status_code) { + *out_status_code = AMDUAT_PEL_KERNEL_STATUS_INTERNAL; + } + return false; + } + if (inputs_len == 0) { + if (out_status_code) { + *out_status_code = AMDUAT_PEL_KERNEL_STATUS_INTERNAL; + } + return false; + } + + for (i = 1; i < inputs_len; ++i) { + if (!amduat_type_tags_match(&inputs[0], &inputs[i])) { + if (out_status_code) { + *out_status_code = AMDUAT_PEL_KERNEL_STATUS_CONCAT_TYPE_TAG_MISMATCH; + } + return false; + } + } + + for (i = 0; i < inputs_len; ++i) { + if (inputs[i].bytes.len != 0 && inputs[i].bytes.data == NULL) { + if (out_status_code) { + *out_status_code = AMDUAT_PEL_KERNEL_STATUS_INTERNAL; + } + return false; + } + if (inputs[i].bytes.len > SIZE_MAX - total_len) { + if (out_status_code) { + *out_status_code = AMDUAT_PEL_KERNEL_STATUS_INTERNAL; + } + return false; + } + total_len += inputs[i].bytes.len; + } + + buffer = NULL; + if (total_len != 0) { + buffer = (uint8_t *)malloc(total_len); + if (buffer == NULL) { + if (out_status_code) { + *out_status_code = AMDUAT_PEL_KERNEL_STATUS_INTERNAL; + } + return false; + } + } + + { + size_t offset = 0; + for (i = 0; i < inputs_len; ++i) { + if (inputs[i].bytes.len != 0 && inputs[i].bytes.data != NULL) { + memcpy(buffer + offset, inputs[i].bytes.data, inputs[i].bytes.len); + } + offset += inputs[i].bytes.len; + } + } + + if (!amduat_alloc_outputs(1, out_outputs)) { + free(buffer); + if (out_status_code) { + *out_status_code = AMDUAT_PEL_KERNEL_STATUS_INTERNAL; + } + return false; + } + + (*out_outputs)[0].bytes = amduat_octets(buffer, total_len); + if (inputs[0].has_type_tag) { + (*out_outputs)[0].has_type_tag = true; + (*out_outputs)[0].type_tag = inputs[0].type_tag; + } else { + (*out_outputs)[0].has_type_tag = false; + (*out_outputs)[0].type_tag.tag_id = 0; + } + if (out_outputs_len) { + *out_outputs_len = 1; + } + return true; +} + +static bool amduat_kernel_slice(const amduat_artifact_t *inputs, + size_t inputs_len, + const amduat_pel_kernel_slice_params_t *params, + amduat_artifact_t **out_outputs, + size_t *out_outputs_len, + uint32_t *out_status_code) { + const amduat_artifact_t *input; + uint64_t offset; + uint64_t length; + uint64_t total_len; + uint8_t *buffer; + + if (inputs == NULL) { + if (out_status_code) { + *out_status_code = AMDUAT_PEL_KERNEL_STATUS_INTERNAL; + } + return false; + } + if (inputs_len != 1 || params == NULL) { + if (out_status_code) { + *out_status_code = AMDUAT_PEL_KERNEL_STATUS_INTERNAL; + } + return false; + } + + input = &inputs[0]; + total_len = (uint64_t)input->bytes.len; + offset = params->offset; + length = params->length; + if (offset > total_len || length > total_len - offset) { + if (out_status_code) { + *out_status_code = AMDUAT_PEL_KERNEL_STATUS_SLICE_RANGE_OUT_OF_BOUNDS; + } + return false; + } + + buffer = NULL; + if (length != 0) { + buffer = (uint8_t *)malloc((size_t)length); + if (buffer == NULL) { + if (out_status_code) { + *out_status_code = AMDUAT_PEL_KERNEL_STATUS_INTERNAL; + } + return false; + } + if (input->bytes.data == NULL) { + free(buffer); + if (out_status_code) { + *out_status_code = AMDUAT_PEL_KERNEL_STATUS_INTERNAL; + } + return false; + } + memcpy(buffer, input->bytes.data + (size_t)offset, (size_t)length); + } + + if (!amduat_alloc_outputs(1, out_outputs)) { + free(buffer); + if (out_status_code) { + *out_status_code = AMDUAT_PEL_KERNEL_STATUS_INTERNAL; + } + return false; + } + + (*out_outputs)[0].bytes = amduat_octets(buffer, (size_t)length); + if (input->has_type_tag) { + (*out_outputs)[0].has_type_tag = true; + (*out_outputs)[0].type_tag = input->type_tag; + } else { + (*out_outputs)[0].has_type_tag = false; + (*out_outputs)[0].type_tag.tag_id = 0; + } + if (out_outputs_len) { + *out_outputs_len = 1; + } + return true; +} + +static bool amduat_kernel_const(const amduat_pel_kernel_const_params_t *params, + amduat_artifact_t **out_outputs, + size_t *out_outputs_len, + uint32_t *out_status_code) { + uint8_t *buffer; + + if (params == NULL) { + if (out_status_code) { + *out_status_code = AMDUAT_PEL_KERNEL_STATUS_INTERNAL; + } + return false; + } + if (params->bytes.len != 0 && params->bytes.data == NULL) { + if (out_status_code) { + *out_status_code = AMDUAT_PEL_KERNEL_STATUS_INTERNAL; + } + return false; + } + + buffer = NULL; + if (params->bytes.len != 0) { + buffer = (uint8_t *)malloc(params->bytes.len); + if (buffer == NULL) { + if (out_status_code) { + *out_status_code = AMDUAT_PEL_KERNEL_STATUS_INTERNAL; + } + return false; + } + if (params->bytes.data != NULL) { + memcpy(buffer, params->bytes.data, params->bytes.len); + } + } + + if (!amduat_alloc_outputs(1, out_outputs)) { + free(buffer); + if (out_status_code) { + *out_status_code = AMDUAT_PEL_KERNEL_STATUS_INTERNAL; + } + return false; + } + + (*out_outputs)[0].bytes = amduat_octets(buffer, params->bytes.len); + if (params->has_type_tag) { + (*out_outputs)[0].has_type_tag = true; + (*out_outputs)[0].type_tag.tag_id = params->tag_id; + } else { + (*out_outputs)[0].has_type_tag = false; + (*out_outputs)[0].type_tag.tag_id = 0; + } + if (out_outputs_len) { + *out_outputs_len = 1; + } + return true; +} + +static bool amduat_kernel_hash(const amduat_artifact_t *inputs, + size_t inputs_len, + const amduat_pel_kernel_hash_params_t *params, + amduat_artifact_t **out_outputs, + size_t *out_outputs_len, + uint32_t *out_status_code) { + const amduat_hash_asl1_desc_t *desc; + uint8_t *buffer; + + if (inputs == NULL) { + if (out_status_code) { + *out_status_code = AMDUAT_PEL_KERNEL_STATUS_INTERNAL; + } + return false; + } + if (inputs_len != 1 || params == NULL) { + if (out_status_code) { + *out_status_code = AMDUAT_PEL_KERNEL_STATUS_INTERNAL; + } + return false; + } + + desc = amduat_hash_asl1_desc_lookup(params->hash_id); + if (desc == NULL || desc->digest_len == 0) { + if (out_status_code) { + *out_status_code = AMDUAT_PEL_KERNEL_STATUS_INTERNAL; + } + return false; + } + if (inputs[0].bytes.len != 0 && inputs[0].bytes.data == NULL) { + if (out_status_code) { + *out_status_code = AMDUAT_PEL_KERNEL_STATUS_INTERNAL; + } + return false; + } + + buffer = (uint8_t *)malloc(desc->digest_len); + if (buffer == NULL) { + if (out_status_code) { + *out_status_code = AMDUAT_PEL_KERNEL_STATUS_INTERNAL; + } + return false; + } + + if (!amduat_hash_asl1_digest(params->hash_id, inputs[0].bytes, buffer, + desc->digest_len)) { + free(buffer); + if (out_status_code) { + *out_status_code = AMDUAT_PEL_KERNEL_STATUS_INTERNAL; + } + return false; + } + + if (!amduat_alloc_outputs(1, out_outputs)) { + free(buffer); + if (out_status_code) { + *out_status_code = AMDUAT_PEL_KERNEL_STATUS_INTERNAL; + } + return false; + } + + (*out_outputs)[0].bytes = amduat_octets(buffer, desc->digest_len); + (*out_outputs)[0].has_type_tag = false; + (*out_outputs)[0].type_tag.tag_id = 0; + if (out_outputs_len) { + *out_outputs_len = 1; + } + return true; +} + +bool amduat_pel_kernel_op_eval( + const amduat_pel_kernel_op_desc_t *desc, + const amduat_artifact_t *inputs, + size_t inputs_len, + const amduat_pel_kernel_params_t *params, + amduat_artifact_t **out_outputs, + size_t *out_outputs_len, + uint32_t *out_status_code) { + if (out_outputs_len != NULL) { + *out_outputs_len = 0; + } + if (out_outputs != NULL) { + *out_outputs = NULL; + } + + if (desc == NULL || out_status_code == NULL) { + return false; + } + if (params == NULL) { + *out_status_code = AMDUAT_PEL_KERNEL_STATUS_INTERNAL; + return false; + } + + switch (desc->kind) { + case AMDUAT_PEL_KERNEL_OP_CONCAT: + return amduat_kernel_concat(inputs, inputs_len, out_outputs, + out_outputs_len, out_status_code); + case AMDUAT_PEL_KERNEL_OP_SLICE: + return amduat_kernel_slice(inputs, inputs_len, ¶ms->value.slice, + out_outputs, out_outputs_len, out_status_code); + case AMDUAT_PEL_KERNEL_OP_CONST: + return amduat_kernel_const(¶ms->value.konst, out_outputs, + out_outputs_len, out_status_code); + case AMDUAT_PEL_KERNEL_OP_HASH_ASL1: + return amduat_kernel_hash(inputs, inputs_len, ¶ms->value.hash, + out_outputs, out_outputs_len, out_status_code); + default: + *out_status_code = AMDUAT_PEL_KERNEL_STATUS_INTERNAL; + return false; + } +} diff --git a/src/pel_stack/opreg/kernel_params.c b/src/pel_stack/opreg/kernel_params.c index e69de29..dbdd364 100644 --- a/src/pel_stack/opreg/kernel_params.c +++ b/src/pel_stack/opreg/kernel_params.c @@ -0,0 +1,132 @@ +#include "amduat/pel/opreg_kernel_params.h" + +#include +#include +#include + +static uint16_t amduat_load_u16_be(const uint8_t *data) { + return (uint16_t)((data[0] << 8) | data[1]); +} + +static uint32_t amduat_load_u32_be(const uint8_t *data) { + return ((uint32_t)data[0] << 24) | ((uint32_t)data[1] << 16) | + ((uint32_t)data[2] << 8) | (uint32_t)data[3]; +} + +static uint64_t amduat_load_u64_be(const uint8_t *data) { + return ((uint64_t)data[0] << 56) | ((uint64_t)data[1] << 48) | + ((uint64_t)data[2] << 40) | ((uint64_t)data[3] << 32) | + ((uint64_t)data[4] << 24) | ((uint64_t)data[5] << 16) | + ((uint64_t)data[6] << 8) | (uint64_t)data[7]; +} + +static bool amduat_decode_unit(amduat_octets_t bytes) { + return bytes.len == 0; +} + +static bool amduat_decode_slice(amduat_octets_t bytes, + amduat_pel_kernel_slice_params_t *out_params) { + if (out_params == NULL) { + return false; + } + if (bytes.len != 16 || bytes.data == NULL) { + return false; + } + out_params->offset = amduat_load_u64_be(bytes.data); + out_params->length = amduat_load_u64_be(bytes.data + 8); + return true; +} + +static bool amduat_decode_const(amduat_octets_t bytes, + amduat_pel_kernel_const_params_t *out_params) { + const uint8_t *data; + uint64_t payload_len; + uint64_t expected_total; + size_t offset; + + if (out_params == NULL) { + return false; + } + if (bytes.len < 1 || bytes.data == NULL) { + return false; + } + + data = bytes.data; + offset = 0; + if (data[offset] == 0x00u) { + out_params->has_type_tag = false; + offset += 1; + if (bytes.len < offset + 8) { + return false; + } + payload_len = amduat_load_u64_be(data + offset); + offset += 8; + expected_total = (uint64_t)offset + payload_len; + if (payload_len > SIZE_MAX || expected_total > SIZE_MAX || + expected_total != bytes.len) { + return false; + } + out_params->bytes = amduat_octets(data + offset, (size_t)payload_len); + out_params->tag_id = 0; + return true; + } + + if (data[offset] == 0x01u) { + out_params->has_type_tag = true; + offset += 1; + if (bytes.len < offset + 4 + 8) { + return false; + } + out_params->tag_id = amduat_load_u32_be(data + offset); + offset += 4; + payload_len = amduat_load_u64_be(data + offset); + offset += 8; + expected_total = (uint64_t)offset + payload_len; + if (payload_len > SIZE_MAX || expected_total > SIZE_MAX || + expected_total != bytes.len) { + return false; + } + out_params->bytes = amduat_octets(data + offset, (size_t)payload_len); + return true; + } + + return false; +} + +static bool amduat_decode_hash(amduat_octets_t bytes, + amduat_pel_kernel_hash_params_t *out_params) { + if (out_params == NULL) { + return false; + } + if (bytes.len != 2 || bytes.data == NULL) { + return false; + } + out_params->hash_id = (amduat_hash_id_t)amduat_load_u16_be(bytes.data); + if (out_params->hash_id != 0x0001u) { + return false; + } + return true; +} + +bool amduat_pel_kernel_params_decode( + const amduat_pel_kernel_op_desc_t *desc, + amduat_octets_t params_bytes, + amduat_pel_kernel_params_t *out_params) { + if (desc == NULL || out_params == NULL) { + return false; + } + + out_params->kind = desc->kind; + switch (desc->kind) { + case AMDUAT_PEL_KERNEL_OP_CONCAT: + return amduat_decode_unit(params_bytes); + case AMDUAT_PEL_KERNEL_OP_SLICE: + return amduat_decode_slice(params_bytes, &out_params->value.slice); + case AMDUAT_PEL_KERNEL_OP_CONST: + return amduat_decode_const(params_bytes, &out_params->value.konst); + case AMDUAT_PEL_KERNEL_OP_HASH_ASL1: + return amduat_decode_hash(params_bytes, &out_params->value.hash); + default: + return false; + } +} diff --git a/src/pel_stack/program_dag/program_dag.c b/src/pel_stack/program_dag/program_dag.c index e69de29..c7774b3 100644 --- a/src/pel_stack/program_dag/program_dag.c +++ b/src/pel_stack/program_dag/program_dag.c @@ -0,0 +1,642 @@ +#include "amduat/pel/program_dag.h" + +#include "amduat/pel/opreg_kernel.h" +#include "amduat/pel/opreg_kernel_params.h" +#include "amduat/pel/program_dag_desc.h" + +#include +#include +#include +#include +#include + +typedef struct { + amduat_artifact_t *outputs; + size_t outputs_len; +} amduat_pel_node_outputs_t; + +typedef struct { + size_t *order; + const amduat_pel_kernel_op_desc_t **ops; + amduat_pel_kernel_params_t *params; +} amduat_pel_program_dag_prepared_t; + +static void amduat_prepared_reset(amduat_pel_program_dag_prepared_t *prepared) { + if (prepared == NULL) { + return; + } + prepared->order = NULL; + prepared->ops = NULL; + prepared->params = NULL; +} + +static void amduat_prepared_free(amduat_pel_program_dag_prepared_t *prepared) { + if (prepared == NULL) { + return; + } + free(prepared->order); + free(prepared->ops); + free(prepared->params); + amduat_prepared_reset(prepared); +} + +static void amduat_set_result(amduat_pel_execution_result_value_t *result, + amduat_pel_execution_status_t status, + amduat_pel_execution_error_kind_t kind, + uint32_t status_code) { + if (result == NULL) { + return; + } + result->pel1_version = 1; + result->status = status; + result->scheme_ref = amduat_pel_program_dag_scheme_ref(); + result->summary.kind = kind; + result->summary.status_code = status_code; + result->diagnostics = NULL; + result->diagnostics_len = 0; +} + +static bool amduat_utf8_is_valid(amduat_octets_t value) { + size_t i = 0; + + while (i < value.len) { + uint8_t c = value.data[i]; + if (c <= 0x7f) { + i += 1; + continue; + } + if ((c & 0xe0u) == 0xc0u) { + if (i + 1 >= value.len) { + return false; + } + if ((value.data[i + 1] & 0xc0u) != 0x80u) { + return false; + } + if (c < 0xc2u) { + return false; + } + i += 2; + continue; + } + if ((c & 0xf0u) == 0xe0u) { + if (i + 2 >= value.len) { + return false; + } + if ((value.data[i + 1] & 0xc0u) != 0x80u || + (value.data[i + 2] & 0xc0u) != 0x80u) { + return false; + } + if (c == 0xe0u && value.data[i + 1] < 0xa0u) { + return false; + } + if (c == 0xedu && value.data[i + 1] >= 0xa0u) { + return false; + } + i += 3; + continue; + } + if ((c & 0xf8u) == 0xf0u) { + if (i + 3 >= value.len) { + return false; + } + if ((value.data[i + 1] & 0xc0u) != 0x80u || + (value.data[i + 2] & 0xc0u) != 0x80u || + (value.data[i + 3] & 0xc0u) != 0x80u) { + return false; + } + if (c == 0xf0u && value.data[i + 1] < 0x90u) { + return false; + } + if (c == 0xf4u && value.data[i + 1] >= 0x90u) { + return false; + } + if (c > 0xf4u) { + return false; + } + i += 4; + continue; + } + return false; + } + + return true; +} + +static int amduat_find_node_index(const amduat_pel_program_t *program, + amduat_pel_node_id_t node_id) { + size_t i; + + for (i = 0; i < program->nodes_len; ++i) { + if (program->nodes[i].id == node_id) { + return (int)i; + } + } + + return -1; +} + +static bool amduat_build_node_order(const amduat_pel_program_t *program, + size_t *out_order) { + size_t n; + size_t *deps; + bool *placed; + size_t i; + + n = program->nodes_len; + deps = NULL; + placed = NULL; + + if (n == 0) { + return true; + } + + deps = (size_t *)calloc(n, sizeof(*deps)); + placed = (bool *)calloc(n, sizeof(*placed)); + if (deps == NULL || placed == NULL) { + free(deps); + free(placed); + return false; + } + + for (i = 0; i < n; ++i) { + size_t j; + for (j = i + 1; j < n; ++j) { + if (program->nodes[i].id == program->nodes[j].id) { + free(deps); + free(placed); + return false; + } + } + } + + for (i = 0; i < n; ++i) { + size_t j; + const amduat_pel_node_t *node; + + node = &program->nodes[i]; + for (j = 0; j < node->inputs_len; ++j) { + const amduat_pel_dag_input_t *input = &node->inputs[j]; + if (input->kind == AMDUAT_PEL_DAG_INPUT_NODE) { + if (amduat_find_node_index(program, input->value.node.node_id) < 0) { + free(deps); + free(placed); + return false; + } + deps[i] += 1; + } else if (input->kind != AMDUAT_PEL_DAG_INPUT_EXTERNAL) { + free(deps); + free(placed); + return false; + } + } + } + + for (i = 0; i < n; ++i) { + size_t best = SIZE_MAX; + size_t j; + amduat_pel_node_id_t best_id = 0; + + for (j = 0; j < n; ++j) { + if (placed[j] || deps[j] != 0) { + continue; + } + if (best == SIZE_MAX || program->nodes[j].id < best_id) { + best = j; + best_id = program->nodes[j].id; + } + } + + if (best == SIZE_MAX) { + free(deps); + free(placed); + return false; + } + + if (out_order != NULL) { + out_order[i] = best; + } + placed[best] = true; + + for (j = 0; j < n; ++j) { + size_t k; + const amduat_pel_node_t *node; + + if (placed[j]) { + continue; + } + node = &program->nodes[j]; + for (k = 0; k < node->inputs_len; ++k) { + const amduat_pel_dag_input_t *input = &node->inputs[k]; + if (input->kind == AMDUAT_PEL_DAG_INPUT_NODE && + input->value.node.node_id == program->nodes[best].id) { + if (deps[j] == 0) { + free(deps); + free(placed); + return false; + } + deps[j] -= 1; + } + } + } + } + + free(deps); + free(placed); + return true; +} + +static void amduat_free_node_outputs(amduat_pel_node_outputs_t *node_outputs, + size_t len) { + size_t i; + + if (node_outputs == NULL) { + return; + } + + for (i = 0; i < len; ++i) { + amduat_pel_node_outputs_t *entry = &node_outputs[i]; + size_t j; + for (j = 0; j < entry->outputs_len; ++j) { + free((void *)entry->outputs[j].bytes.data); + entry->outputs[j].bytes.data = NULL; + entry->outputs[j].bytes.len = 0; + } + free(entry->outputs); + entry->outputs = NULL; + entry->outputs_len = 0; + } + free(node_outputs); +} + +static bool amduat_copy_artifact(amduat_artifact_t *out, + const amduat_artifact_t *src) { + uint8_t *buffer = NULL; + + if (out == NULL || src == NULL) { + return false; + } + + if (src->bytes.len != 0) { + buffer = (uint8_t *)malloc(src->bytes.len); + if (buffer == NULL) { + return false; + } + if (src->bytes.data != NULL) { + memcpy(buffer, src->bytes.data, src->bytes.len); + } + } + + out->bytes = amduat_octets(buffer, src->bytes.len); + out->has_type_tag = src->has_type_tag; + out->type_tag = src->type_tag; + return true; +} + +static bool amduat_program_prepare(const amduat_pel_program_t *program, + amduat_pel_program_dag_prepared_t *prepared) { + size_t i; + + if (program == NULL || prepared == NULL) { + return false; + } + + amduat_prepared_reset(prepared); + + if (program->nodes_len > 0 && program->nodes == NULL) { + return false; + } + if (program->roots_len > 0 && program->roots == NULL) { + return false; + } + + if (program->nodes_len == 0) { + if (program->roots_len != 0) { + return false; + } + return true; + } + + prepared->order = (size_t *)malloc(program->nodes_len * + sizeof(*prepared->order)); + prepared->ops = (const amduat_pel_kernel_op_desc_t **)calloc( + program->nodes_len, sizeof(*prepared->ops)); + prepared->params = (amduat_pel_kernel_params_t *)calloc( + program->nodes_len, sizeof(*prepared->params)); + + if (prepared->order == NULL || prepared->ops == NULL || + prepared->params == NULL) { + amduat_prepared_free(prepared); + return false; + } + + for (i = 0; i < program->nodes_len; ++i) { + const amduat_pel_node_t *node = &program->nodes[i]; + const amduat_pel_kernel_op_desc_t *desc; + + if (node->op.name.len > 0 && node->op.name.data == NULL) { + amduat_prepared_free(prepared); + return false; + } + if (!amduat_utf8_is_valid(node->op.name)) { + amduat_prepared_free(prepared); + return false; + } + if (node->inputs_len > 0 && node->inputs == NULL) { + amduat_prepared_free(prepared); + return false; + } + if (node->params.len > 0 && node->params.data == NULL) { + amduat_prepared_free(prepared); + return false; + } + + desc = amduat_pel_kernel_op_lookup(node->op.name, node->op.version); + if (desc == NULL) { + amduat_prepared_free(prepared); + return false; + } + if (node->inputs_len < desc->min_inputs || + node->inputs_len > desc->max_inputs) { + amduat_prepared_free(prepared); + return false; + } + if (!amduat_pel_kernel_params_decode(desc, node->params, + &prepared->params[i])) { + amduat_prepared_free(prepared); + return false; + } + + prepared->ops[i] = desc; + } + + if (!amduat_build_node_order(program, prepared->order)) { + amduat_prepared_free(prepared); + return false; + } + + for (i = 0; i < program->nodes_len; ++i) { + const amduat_pel_node_t *node = &program->nodes[i]; + size_t j; + for (j = 0; j < node->inputs_len; ++j) { + const amduat_pel_dag_input_t *input = &node->inputs[j]; + if (input->kind == AMDUAT_PEL_DAG_INPUT_NODE) { + int dep_index = amduat_find_node_index(program, + input->value.node.node_id); + if (dep_index < 0) { + amduat_prepared_free(prepared); + return false; + } + if (input->value.node.output_index >= + prepared->ops[dep_index]->outputs_len) { + amduat_prepared_free(prepared); + return false; + } + } else if (input->kind != AMDUAT_PEL_DAG_INPUT_EXTERNAL) { + amduat_prepared_free(prepared); + return false; + } + } + } + + for (i = 0; i < program->roots_len; ++i) { + int root_index = amduat_find_node_index(program, program->roots[i].node_id); + if (root_index < 0) { + amduat_prepared_free(prepared); + return false; + } + if (program->roots[i].output_index >= + prepared->ops[root_index]->outputs_len) { + amduat_prepared_free(prepared); + return false; + } + } + + return true; +} + +bool amduat_pel_program_dag_validate(const amduat_pel_program_t *program) { + amduat_pel_program_dag_prepared_t prepared; + bool ok; + + amduat_prepared_reset(&prepared); + ok = amduat_program_prepare(program, &prepared); + amduat_prepared_free(&prepared); + return ok; +} + +bool amduat_pel_program_dag_exec( + const amduat_pel_program_t *program, + const amduat_artifact_t *inputs, + size_t inputs_len, + amduat_artifact_t **out_outputs, + size_t *out_outputs_len, + amduat_pel_execution_result_value_t *out_result) { + amduat_pel_program_dag_prepared_t prepared; + amduat_pel_node_outputs_t *node_outputs; + amduat_artifact_t *resolved_inputs; + size_t max_inputs; + size_t i; + + if (out_outputs == NULL || out_outputs_len == NULL || out_result == NULL) { + return false; + } + + *out_outputs = NULL; + *out_outputs_len = 0; + + amduat_prepared_reset(&prepared); + if (!amduat_program_prepare(program, &prepared)) { + amduat_set_result(out_result, AMDUAT_PEL_EXEC_STATUS_INVALID_PROGRAM, + AMDUAT_PEL_EXEC_ERROR_PROGRAM, 2); + return true; + } + + if (inputs_len > 0 && inputs == NULL) { + bool needs_inputs = false; + for (i = 0; i < program->nodes_len; ++i) { + size_t j; + const amduat_pel_node_t *node = &program->nodes[i]; + for (j = 0; j < node->inputs_len; ++j) { + if (node->inputs[j].kind == AMDUAT_PEL_DAG_INPUT_EXTERNAL) { + needs_inputs = true; + break; + } + } + if (needs_inputs) { + break; + } + } + if (needs_inputs) { + amduat_set_result(out_result, AMDUAT_PEL_EXEC_STATUS_INVALID_INPUTS, + AMDUAT_PEL_EXEC_ERROR_INPUTS, 3); + amduat_prepared_free(&prepared); + return true; + } + } + + if (program->nodes_len == 0) { + amduat_set_result(out_result, AMDUAT_PEL_EXEC_STATUS_OK, + AMDUAT_PEL_EXEC_ERROR_NONE, 0); + amduat_prepared_free(&prepared); + return true; + } + + node_outputs = (amduat_pel_node_outputs_t *)calloc( + program->nodes_len, sizeof(*node_outputs)); + if (node_outputs == NULL) { + amduat_set_result(out_result, AMDUAT_PEL_EXEC_STATUS_RUNTIME_FAILED, + AMDUAT_PEL_EXEC_ERROR_RUNTIME, 1); + amduat_prepared_free(&prepared); + return true; + } + + max_inputs = 0; + for (i = 0; i < program->nodes_len; ++i) { + if (program->nodes[i].inputs_len > max_inputs) { + max_inputs = program->nodes[i].inputs_len; + } + } + + resolved_inputs = NULL; + if (max_inputs != 0) { + resolved_inputs = (amduat_artifact_t *)malloc( + max_inputs * sizeof(*resolved_inputs)); + if (resolved_inputs == NULL) { + amduat_set_result(out_result, AMDUAT_PEL_EXEC_STATUS_RUNTIME_FAILED, + AMDUAT_PEL_EXEC_ERROR_RUNTIME, 1); + amduat_free_node_outputs(node_outputs, program->nodes_len); + amduat_prepared_free(&prepared); + return true; + } + } + + for (i = 0; i < program->nodes_len; ++i) { + size_t node_index = prepared.order[i]; + const amduat_pel_node_t *node = &program->nodes[node_index]; + size_t j; + uint32_t status_code = 0; + + for (j = 0; j < node->inputs_len; ++j) { + const amduat_pel_dag_input_t *input = &node->inputs[j]; + if (input->kind == AMDUAT_PEL_DAG_INPUT_EXTERNAL) { + if (input->value.external.input_index >= inputs_len) { + amduat_set_result(out_result, AMDUAT_PEL_EXEC_STATUS_INVALID_INPUTS, + AMDUAT_PEL_EXEC_ERROR_INPUTS, 3); + free(resolved_inputs); + amduat_free_node_outputs(node_outputs, program->nodes_len); + amduat_prepared_free(&prepared); + return true; + } + resolved_inputs[j] = inputs[input->value.external.input_index]; + } else if (input->kind == AMDUAT_PEL_DAG_INPUT_NODE) { + int dep_index = amduat_find_node_index(program, + input->value.node.node_id); + if (dep_index < 0 || + input->value.node.output_index >= + node_outputs[dep_index].outputs_len) { + amduat_set_result(out_result, AMDUAT_PEL_EXEC_STATUS_INVALID_PROGRAM, + AMDUAT_PEL_EXEC_ERROR_PROGRAM, 2); + free(resolved_inputs); + amduat_free_node_outputs(node_outputs, program->nodes_len); + amduat_prepared_free(&prepared); + return true; + } + resolved_inputs[j] = + node_outputs[dep_index].outputs[input->value.node.output_index]; + } else { + amduat_set_result(out_result, AMDUAT_PEL_EXEC_STATUS_INVALID_PROGRAM, + AMDUAT_PEL_EXEC_ERROR_PROGRAM, 2); + free(resolved_inputs); + amduat_free_node_outputs(node_outputs, program->nodes_len); + amduat_prepared_free(&prepared); + return true; + } + } + + if (!amduat_pel_kernel_op_eval( + prepared.ops[node_index], resolved_inputs, node->inputs_len, + &prepared.params[node_index], &node_outputs[node_index].outputs, + &node_outputs[node_index].outputs_len, &status_code)) { + if (status_code == 2 || status_code == 3 || status_code == 0) { + status_code = 1; + } + amduat_set_result(out_result, AMDUAT_PEL_EXEC_STATUS_RUNTIME_FAILED, + AMDUAT_PEL_EXEC_ERROR_RUNTIME, status_code); + free(resolved_inputs); + amduat_free_node_outputs(node_outputs, program->nodes_len); + amduat_prepared_free(&prepared); + return true; + } + } + + if (program->roots_len != 0) { + *out_outputs = (amduat_artifact_t *)calloc( + program->roots_len, sizeof(**out_outputs)); + if (*out_outputs == NULL) { + amduat_set_result(out_result, AMDUAT_PEL_EXEC_STATUS_RUNTIME_FAILED, + AMDUAT_PEL_EXEC_ERROR_RUNTIME, 1); + free(resolved_inputs); + amduat_free_node_outputs(node_outputs, program->nodes_len); + amduat_prepared_free(&prepared); + return true; + } + } + + for (i = 0; i < program->roots_len; ++i) { + int root_index = amduat_find_node_index(program, program->roots[i].node_id); + if (root_index < 0 || + program->roots[i].output_index >= + node_outputs[root_index].outputs_len) { + amduat_set_result(out_result, AMDUAT_PEL_EXEC_STATUS_INVALID_PROGRAM, + AMDUAT_PEL_EXEC_ERROR_PROGRAM, 2); + amduat_pel_program_dag_free_outputs(*out_outputs, + program->roots_len); + *out_outputs = NULL; + *out_outputs_len = 0; + free(resolved_inputs); + amduat_free_node_outputs(node_outputs, program->nodes_len); + amduat_prepared_free(&prepared); + return true; + } + if (!amduat_copy_artifact( + &(*out_outputs)[i], + &node_outputs[root_index] + .outputs[program->roots[i].output_index])) { + amduat_set_result(out_result, AMDUAT_PEL_EXEC_STATUS_RUNTIME_FAILED, + AMDUAT_PEL_EXEC_ERROR_RUNTIME, 1); + amduat_pel_program_dag_free_outputs(*out_outputs, + program->roots_len); + *out_outputs = NULL; + *out_outputs_len = 0; + free(resolved_inputs); + amduat_free_node_outputs(node_outputs, program->nodes_len); + amduat_prepared_free(&prepared); + return true; + } + } + + *out_outputs_len = program->roots_len; + amduat_set_result(out_result, AMDUAT_PEL_EXEC_STATUS_OK, + AMDUAT_PEL_EXEC_ERROR_NONE, 0); + + free(resolved_inputs); + amduat_free_node_outputs(node_outputs, program->nodes_len); + amduat_prepared_free(&prepared); + return true; +} + +void amduat_pel_program_dag_free_outputs(amduat_artifact_t *outputs, + size_t outputs_len) { + size_t i; + + if (outputs == NULL) { + return; + } + + for (i = 0; i < outputs_len; ++i) { + free((void *)outputs[i].bytes.data); + outputs[i].bytes.data = NULL; + outputs[i].bytes.len = 0; + } + free(outputs); +} diff --git a/src/pel_stack/program_dag/program_dag_desc.c b/src/pel_stack/program_dag/program_dag_desc.c index e69de29..50abf7f 100644 --- a/src/pel_stack/program_dag/program_dag_desc.c +++ b/src/pel_stack/program_dag/program_dag_desc.c @@ -0,0 +1,15 @@ +#include "amduat/pel/program_dag_desc.h" + +#include "amduat/hash/asl1.h" + +amduat_reference_t amduat_pel_program_dag_scheme_ref(void) { + static const uint8_t digest[] = { + 0xc5, 0x0f, 0xb2, 0xa7, 0x34, 0xa5, 0xcc, 0x23, + 0x3c, 0x38, 0x75, 0xb7, 0x0a, 0x7d, 0x96, 0xea, + 0xad, 0x37, 0x4f, 0x00, 0x00, 0x29, 0x77, 0x1d, + 0x8b, 0xef, 0x1a, 0xf2, 0xcd, 0x63, 0x84, 0xdd, + }; + + return amduat_reference(AMDUAT_HASH_ASL1_ID_SHA256, + amduat_octets(digest, sizeof(digest))); +} diff --git a/tests/pel/test_pel_program_dag_exec.c b/tests/pel/test_pel_program_dag_exec.c new file mode 100644 index 0000000..5becc86 --- /dev/null +++ b/tests/pel/test_pel_program_dag_exec.c @@ -0,0 +1,296 @@ +#include "amduat/pel/program_dag.h" +#include +#include +#include +#include +#include + +static void store_u64_be(uint8_t *out, uint64_t value) { + out[0] = (uint8_t)((value >> 56) & 0xffu); + out[1] = (uint8_t)((value >> 48) & 0xffu); + out[2] = (uint8_t)((value >> 40) & 0xffu); + out[3] = (uint8_t)((value >> 32) & 0xffu); + out[4] = (uint8_t)((value >> 24) & 0xffu); + out[5] = (uint8_t)((value >> 16) & 0xffu); + out[6] = (uint8_t)((value >> 8) & 0xffu); + out[7] = (uint8_t)(value & 0xffu); +} + +static amduat_octets_t make_const_params(const uint8_t *bytes, size_t len) { + size_t total = 1 + 8 + len; + uint8_t *buffer = (uint8_t *)malloc(total); + if (buffer == NULL) { + return amduat_octets(NULL, 0); + } + buffer[0] = 0x00u; + store_u64_be(buffer + 1, (uint64_t)len); + if (len != 0 && bytes != NULL) { + memcpy(buffer + 1 + 8, bytes, len); + } + return amduat_octets(buffer, total); +} + +static bool bytes_equal(amduat_octets_t bytes, + const uint8_t *expected, + size_t expected_len) { + if (bytes.len != expected_len) { + return false; + } + if (bytes.len == 0) { + return true; + } + return memcmp(bytes.data, expected, expected_len) == 0; +} + +static int test_valid_program(void) { + amduat_pel_dag_input_t node2_inputs[2]; + amduat_pel_node_t nodes[2]; + amduat_pel_root_ref_t roots[1]; + amduat_pel_program_t program; + amduat_artifact_t inputs[1]; + amduat_artifact_t *outputs = NULL; + size_t outputs_len = 0; + amduat_pel_execution_result_value_t result; + const char op_const[] = "pel.bytes.const"; + const char op_concat[] = "pel.bytes.concat"; + const uint8_t const_bytes[] = {'h', 'i'}; + const uint8_t expected[] = {'h', 'i', '!'}; + amduat_octets_t const_params; + int exit_code = 1; + + const_params = make_const_params(const_bytes, sizeof(const_bytes)); + if (const_params.data == NULL) { + fprintf(stderr, "const params alloc failed\n"); + return exit_code; + } + + nodes[0].id = 2; + nodes[0].op.name = amduat_octets(op_concat, strlen(op_concat)); + nodes[0].op.version = 1; + nodes[0].inputs = node2_inputs; + nodes[0].inputs_len = 2; + nodes[0].params = amduat_octets(NULL, 0); + + nodes[1].id = 1; + nodes[1].op.name = amduat_octets(op_const, strlen(op_const)); + nodes[1].op.version = 1; + nodes[1].inputs = NULL; + nodes[1].inputs_len = 0; + nodes[1].params = const_params; + + node2_inputs[0].kind = AMDUAT_PEL_DAG_INPUT_NODE; + node2_inputs[0].value.node.node_id = 1; + node2_inputs[0].value.node.output_index = 0; + node2_inputs[1].kind = AMDUAT_PEL_DAG_INPUT_EXTERNAL; + node2_inputs[1].value.external.input_index = 0; + + roots[0].node_id = 2; + roots[0].output_index = 0; + + program.nodes = nodes; + program.nodes_len = 2; + program.roots = roots; + program.roots_len = 1; + + inputs[0] = amduat_artifact(amduat_octets("!", 1)); + + if (!amduat_pel_program_dag_exec(&program, inputs, 1, &outputs, + &outputs_len, &result)) { + fprintf(stderr, "exec failed\n"); + goto cleanup; + } + + if (result.status != AMDUAT_PEL_EXEC_STATUS_OK || + result.summary.kind != AMDUAT_PEL_EXEC_ERROR_NONE || + result.summary.status_code != 0) { + fprintf(stderr, "unexpected status\n"); + goto cleanup; + } + + if (outputs_len != 1 || + !bytes_equal(outputs[0].bytes, expected, sizeof(expected))) { + fprintf(stderr, "unexpected output\n"); + goto cleanup; + } + + exit_code = 0; + +cleanup: + amduat_pel_program_dag_free_outputs(outputs, outputs_len); + free((void *)const_params.data); + return exit_code; +} + +static int test_invalid_program_cycle(void) { + amduat_pel_dag_input_t node1_inputs[1]; + amduat_pel_dag_input_t node2_inputs[1]; + amduat_pel_node_t nodes[2]; + amduat_pel_root_ref_t roots[1]; + amduat_pel_program_t program; + amduat_artifact_t *outputs = NULL; + size_t outputs_len = 0; + amduat_pel_execution_result_value_t result; + const char op_concat[] = "pel.bytes.concat"; + + node1_inputs[0].kind = AMDUAT_PEL_DAG_INPUT_NODE; + node1_inputs[0].value.node.node_id = 2; + node1_inputs[0].value.node.output_index = 0; + + node2_inputs[0].kind = AMDUAT_PEL_DAG_INPUT_NODE; + node2_inputs[0].value.node.node_id = 1; + node2_inputs[0].value.node.output_index = 0; + + nodes[0].id = 1; + nodes[0].op.name = amduat_octets(op_concat, strlen(op_concat)); + nodes[0].op.version = 1; + nodes[0].inputs = node1_inputs; + nodes[0].inputs_len = 1; + nodes[0].params = amduat_octets(NULL, 0); + + nodes[1].id = 2; + nodes[1].op.name = amduat_octets(op_concat, strlen(op_concat)); + nodes[1].op.version = 1; + nodes[1].inputs = node2_inputs; + nodes[1].inputs_len = 1; + nodes[1].params = amduat_octets(NULL, 0); + + roots[0].node_id = 1; + roots[0].output_index = 0; + + program.nodes = nodes; + program.nodes_len = 2; + program.roots = roots; + program.roots_len = 1; + + if (!amduat_pel_program_dag_exec(&program, NULL, 0, &outputs, + &outputs_len, &result)) { + fprintf(stderr, "exec failed\n"); + return 1; + } + + if (result.status != AMDUAT_PEL_EXEC_STATUS_INVALID_PROGRAM || + result.summary.kind != AMDUAT_PEL_EXEC_ERROR_PROGRAM || + result.summary.status_code != 2) { + fprintf(stderr, "unexpected invalid program status\n"); + amduat_pel_program_dag_free_outputs(outputs, outputs_len); + return 1; + } + + amduat_pel_program_dag_free_outputs(outputs, outputs_len); + return 0; +} + +static int test_invalid_params(void) { + amduat_pel_dag_input_t node_inputs[1]; + amduat_pel_node_t nodes[1]; + amduat_pel_root_ref_t roots[1]; + amduat_pel_program_t program; + amduat_artifact_t inputs[1]; + amduat_artifact_t *outputs = NULL; + size_t outputs_len = 0; + amduat_pel_execution_result_value_t result; + const char op_concat[] = "pel.bytes.concat"; + uint8_t bad_params[] = {0x01u}; + + node_inputs[0].kind = AMDUAT_PEL_DAG_INPUT_EXTERNAL; + node_inputs[0].value.external.input_index = 0; + + nodes[0].id = 1; + nodes[0].op.name = amduat_octets(op_concat, strlen(op_concat)); + nodes[0].op.version = 1; + nodes[0].inputs = node_inputs; + nodes[0].inputs_len = 1; + nodes[0].params = amduat_octets(bad_params, sizeof(bad_params)); + + roots[0].node_id = 1; + roots[0].output_index = 0; + + program.nodes = nodes; + program.nodes_len = 1; + program.roots = roots; + program.roots_len = 1; + + inputs[0] = amduat_artifact(amduat_octets("x", 1)); + + if (!amduat_pel_program_dag_exec(&program, inputs, 1, &outputs, + &outputs_len, &result)) { + fprintf(stderr, "exec failed\n"); + return 1; + } + + if (result.status != AMDUAT_PEL_EXEC_STATUS_INVALID_PROGRAM || + result.summary.kind != AMDUAT_PEL_EXEC_ERROR_PROGRAM || + result.summary.status_code != 2) { + fprintf(stderr, "unexpected invalid params status\n"); + amduat_pel_program_dag_free_outputs(outputs, outputs_len); + return 1; + } + + amduat_pel_program_dag_free_outputs(outputs, outputs_len); + return 0; +} + +static int test_invalid_input_index(void) { + amduat_pel_dag_input_t node_inputs[1]; + amduat_pel_node_t nodes[1]; + amduat_pel_root_ref_t roots[1]; + amduat_pel_program_t program; + amduat_artifact_t inputs[1]; + amduat_artifact_t *outputs = NULL; + size_t outputs_len = 0; + amduat_pel_execution_result_value_t result; + const char op_concat[] = "pel.bytes.concat"; + + node_inputs[0].kind = AMDUAT_PEL_DAG_INPUT_EXTERNAL; + node_inputs[0].value.external.input_index = 1; + + nodes[0].id = 1; + nodes[0].op.name = amduat_octets(op_concat, strlen(op_concat)); + nodes[0].op.version = 1; + nodes[0].inputs = node_inputs; + nodes[0].inputs_len = 1; + nodes[0].params = amduat_octets(NULL, 0); + + roots[0].node_id = 1; + roots[0].output_index = 0; + + program.nodes = nodes; + program.nodes_len = 1; + program.roots = roots; + program.roots_len = 1; + + inputs[0] = amduat_artifact(amduat_octets("x", 1)); + + if (!amduat_pel_program_dag_exec(&program, inputs, 1, &outputs, + &outputs_len, &result)) { + fprintf(stderr, "exec failed\n"); + return 1; + } + + if (result.status != AMDUAT_PEL_EXEC_STATUS_INVALID_INPUTS || + result.summary.kind != AMDUAT_PEL_EXEC_ERROR_INPUTS || + result.summary.status_code != 3) { + fprintf(stderr, "unexpected invalid inputs status\n"); + amduat_pel_program_dag_free_outputs(outputs, outputs_len); + return 1; + } + + amduat_pel_program_dag_free_outputs(outputs, outputs_len); + return 0; +} + +int main(void) { + if (test_valid_program() != 0) { + return 1; + } + if (test_invalid_program_cycle() != 0) { + return 1; + } + if (test_invalid_params() != 0) { + return 1; + } + if (test_invalid_input_index() != 0) { + return 1; + } + return 0; +}