Add PEL program DAG validation and kernel op execution
This commit is contained in:
parent
585844a6fc
commit
a6425067bf
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
#ifndef AMDUAT_PEL_OPREG_KERNEL_H
|
||||
#define AMDUAT_PEL_OPREG_KERNEL_H
|
||||
|
||||
#include "amduat/asl/core.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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 */
|
||||
|
|
@ -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 <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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 */
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
#define AMDUAT_PEL_PROGRAM_DAG_H
|
||||
|
||||
#include "amduat/asl/core.h"
|
||||
#include "amduat/pel/core.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
#ifndef AMDUAT_PEL_PROGRAM_DAG_DESC_H
|
||||
#define AMDUAT_PEL_PROGRAM_DAG_DESC_H
|
||||
|
||||
#include "amduat/asl/core.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#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 */
|
||||
|
|
@ -0,0 +1,436 @@
|
|||
#include "amduat/pel/opreg_kernel.h"
|
||||
#include "amduat/pel/opreg_kernel_params.h"
|
||||
#include "amduat/hash/asl1.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
#include "amduat/pel/opreg_kernel_params.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
@ -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)));
|
||||
}
|
||||
296
tests/pel/test_pel_program_dag_exec.c
Normal file
296
tests/pel/test_pel_program_dag_exec.c
Normal file
|
|
@ -0,0 +1,296 @@
|
|||
#include "amduat/pel/program_dag.h"
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
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;
|
||||
}
|
||||
Loading…
Reference in a new issue