amduat/tests/enc/test_fps.c

627 lines
15 KiB
C
Raw Permalink Normal View History

2026-02-22 00:25:56 +01:00
#include "amduat/fps.h"
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
const uint8_t *data;
size_t len;
size_t offset;
} cursor_t;
static bool read_file(const char *path, uint8_t **out_bytes, size_t *out_len) {
FILE *fp;
long size;
uint8_t *buffer;
size_t read_len;
if (path == NULL || out_bytes == NULL || out_len == NULL) {
return false;
}
*out_bytes = NULL;
*out_len = 0u;
fp = fopen(path, "rb");
if (fp == NULL) {
return false;
}
if (fseek(fp, 0, SEEK_END) != 0) {
fclose(fp);
return false;
}
size = ftell(fp);
if (size < 0) {
fclose(fp);
return false;
}
if (fseek(fp, 0, SEEK_SET) != 0) {
fclose(fp);
return false;
}
buffer = (uint8_t *)malloc((size_t)size + 1u);
if (buffer == NULL) {
fclose(fp);
return false;
}
read_len = fread(buffer, 1u, (size_t)size, fp);
fclose(fp);
if (read_len != (size_t)size) {
free(buffer);
return false;
}
buffer[size] = '\0';
*out_bytes = buffer;
*out_len = (size_t)size;
return true;
}
static bool build_fixture_path(char *out, size_t out_len) {
const char *needle = "/tests/enc/";
const char *path = __FILE__;
const char *pos = strstr(path, needle);
size_t prefix_len;
if (pos == NULL) {
return false;
}
prefix_len = (size_t)(pos - path);
if (prefix_len + strlen("/tests/fixtures/conformance/"
"fps_1/manifest.json") + 1u >
out_len) {
return false;
}
memcpy(out, path, prefix_len);
out[prefix_len] = '\0';
strcat(out, "/tests/fixtures/conformance/fps_1/manifest.json");
return true;
}
static bool cursor_expect(cursor_t *cur, const char *literal) {
size_t len = strlen(literal);
if (cur->len - cur->offset < len) {
return false;
}
if (memcmp(cur->data + cur->offset, literal, len) != 0) {
return false;
}
cur->offset += len;
return true;
}
static bool cursor_skip_ws(cursor_t *cur) {
while (cur->offset < cur->len) {
char c = (char)cur->data[cur->offset];
if (c != ' ' && c != '\n' && c != '\r' && c != '\t') {
break;
}
cur->offset++;
}
return true;
}
static bool cursor_read_string(cursor_t *cur, char **out) {
size_t start;
size_t end;
char *buf;
if (!cursor_skip_ws(cur) || cur->offset >= cur->len ||
cur->data[cur->offset] != '"') {
return false;
}
cur->offset++;
start = cur->offset;
while (cur->offset < cur->len && cur->data[cur->offset] != '"') {
cur->offset++;
}
if (cur->offset >= cur->len) {
return false;
}
end = cur->offset;
cur->offset++;
buf = (char *)malloc(end - start + 1u);
if (buf == NULL) {
return false;
}
memcpy(buf, cur->data + start, end - start);
buf[end - start] = '\0';
*out = buf;
return true;
}
static bool cursor_read_number(cursor_t *cur, size_t *out) {
size_t start;
size_t value = 0u;
bool has_digit = false;
if (!cursor_skip_ws(cur)) {
return false;
}
start = cur->offset;
while (cur->offset < cur->len) {
char c = (char)cur->data[cur->offset];
if (c < '0' || c > '9') {
break;
}
has_digit = true;
value = value * 10u + (size_t)(c - '0');
cur->offset++;
}
if (!has_digit || start == cur->offset) {
return false;
}
*out = value;
return true;
}
static bool hex_decode(const char *hex, uint8_t **out, size_t *out_len) {
size_t len;
size_t i;
uint8_t *buf;
if (hex == NULL || out == NULL || out_len == NULL) {
return false;
}
len = strlen(hex);
if (len % 2u != 0u) {
return false;
}
buf = (uint8_t *)malloc(len / 2u);
if (buf == NULL) {
return false;
}
for (i = 0u; i < len; i += 2u) {
unsigned int byte;
if (sscanf(hex + i, "%2x", &byte) != 1) {
free(buf);
return false;
}
buf[i / 2u] = (uint8_t)byte;
}
*out = buf;
*out_len = len / 2u;
return true;
}
static bool hex_eq(const char *hex, amduat_octets_t bytes) {
uint8_t *decoded = NULL;
size_t decoded_len = 0u;
bool ok = false;
if (!hex_decode(hex, &decoded, &decoded_len)) {
return false;
}
ok = decoded_len == bytes.len &&
(decoded_len == 0u ||
memcmp(decoded, bytes.data, decoded_len) == 0);
free(decoded);
return ok;
}
static bool cid_matches(const char *hex, amduat_octets_t output) {
amduat_reference_t cid;
size_t hex_len = strlen(hex);
char *lower = NULL;
char *scratch = NULL;
bool ok = false;
if (!amduat_fps_cid_for_payload(AMDUAT_HASH_ASL1_ID_SHA256, output, &cid)) {
return false;
}
lower = (char *)malloc(hex_len + 1u);
if (lower == NULL) {
amduat_reference_free(&cid);
return false;
}
scratch = (char *)malloc(cid.digest.len * 2u + 1u);
if (scratch == NULL) {
free(lower);
amduat_reference_free(&cid);
return false;
}
for (size_t i = 0u; i < hex_len; ++i) {
char c = hex[i];
if (c >= 'A' && c <= 'F') {
c = (char)(c - 'A' + 'a');
}
lower[i] = c;
}
lower[hex_len] = '\0';
for (size_t i = 0u; i < cid.digest.len; ++i) {
sprintf(scratch + i * 2u, "%02x", cid.digest.data[i]);
}
ok = strcmp(lower, scratch) == 0;
free(lower);
free(scratch);
amduat_reference_free(&cid);
return ok;
}
static bool parse_manifest_cases(cursor_t *cur) {
if (!cursor_skip_ws(cur) || cur->offset >= cur->len ||
cur->data[cur->offset] != '[') {
return false;
}
cur->offset++;
while (true) {
char *field = NULL;
char *value = NULL;
char *op = NULL;
char *input = NULL;
char *insert = NULL;
char *output = NULL;
char *cid_hex = NULL;
char *level = NULL;
char *inputs[4] = {0};
size_t inputs_len = 0u;
size_t offset = 0u;
size_t length = 0u;
bool has_offset = false;
bool has_length = false;
bool has_inputs = false;
bool has_input = false;
bool has_insert = false;
bool has_level = false;
bool has_output = false;
bool has_cid = false;
bool ok = true;
if (!cursor_skip_ws(cur)) {
return false;
}
if (cur->offset < cur->len && cur->data[cur->offset] == ']') {
cur->offset++;
return true;
}
if (cur->offset >= cur->len || cur->data[cur->offset] != '{') {
return false;
}
cur->offset++;
while (true) {
if (!cursor_read_string(cur, &field)) {
ok = false;
break;
}
if (!cursor_skip_ws(cur) || cur->offset >= cur->len ||
cur->data[cur->offset] != ':') {
ok = false;
break;
}
cur->offset++;
if (strcmp(field, "id") == 0 ||
strcmp(field, "op") == 0 || strcmp(field, "input") == 0 ||
strcmp(field, "insert") == 0 || strcmp(field, "output") == 0 ||
strcmp(field, "cid_sha256") == 0 || strcmp(field, "level") == 0) {
if (!cursor_read_string(cur, &value)) {
ok = false;
break;
}
if (strcmp(field, "id") == 0) {
free(value);
value = NULL;
} else if (strcmp(field, "op") == 0) {
op = value;
} else if (strcmp(field, "input") == 0) {
input = value;
has_input = true;
} else if (strcmp(field, "insert") == 0) {
insert = value;
has_insert = true;
} else if (strcmp(field, "output") == 0) {
output = value;
has_output = true;
} else if (strcmp(field, "cid_sha256") == 0) {
cid_hex = value;
has_cid = true;
} else {
level = value;
has_level = true;
}
} else if (strcmp(field, "offset") == 0 ||
strcmp(field, "length") == 0) {
size_t num = 0u;
if (!cursor_read_number(cur, &num)) {
ok = false;
break;
}
if (strcmp(field, "offset") == 0) {
offset = num;
has_offset = true;
} else {
length = num;
has_length = true;
}
} else if (strcmp(field, "inputs") == 0) {
if (!cursor_skip_ws(cur) || cur->offset >= cur->len ||
cur->data[cur->offset] != '[') {
ok = false;
break;
}
cur->offset++;
while (true) {
if (!cursor_skip_ws(cur)) {
ok = false;
break;
}
if (cur->offset < cur->len && cur->data[cur->offset] == ']') {
cur->offset++;
break;
}
if (inputs_len >= 4u) {
ok = false;
break;
}
if (!cursor_read_string(cur, &inputs[inputs_len++])) {
ok = false;
break;
}
if (!cursor_skip_ws(cur)) {
ok = false;
break;
}
if (cur->offset < cur->len && cur->data[cur->offset] == ',') {
cur->offset++;
continue;
}
}
has_inputs = true;
} else {
ok = false;
break;
}
free(field);
field = NULL;
if (!cursor_skip_ws(cur)) {
ok = false;
break;
}
if (cur->offset < cur->len && cur->data[cur->offset] == ',') {
cur->offset++;
continue;
}
if (cur->offset < cur->len && cur->data[cur->offset] == '}') {
cur->offset++;
break;
}
}
if (!ok || op == NULL || !has_output || !has_cid) {
ok = false;
}
if (ok) {
amduat_octets_t output_bytes = amduat_octets(NULL, 0u);
amduat_octets_t out = amduat_octets(NULL, 0u);
amduat_octets_t input_bytes = amduat_octets(NULL, 0u);
amduat_octets_t insert_bytes = amduat_octets(NULL, 0u);
uint8_t *decoded = NULL;
size_t decoded_len = 0u;
uint8_t *insert_decoded = NULL;
size_t insert_len = 0u;
if (!hex_decode(output, &decoded, &decoded_len)) {
ok = false;
} else {
output_bytes = amduat_octets(decoded, decoded_len);
}
if (ok && !cid_matches(cid_hex, output_bytes)) {
ok = false;
}
if (ok && strcmp(op, "slice") == 0) {
if (!has_input || !has_offset || !has_length) {
ok = false;
} else if (!hex_decode(input, &decoded, &decoded_len)) {
ok = false;
} else {
input_bytes = amduat_octets(decoded, decoded_len);
if (amduat_fps_slice(input_bytes, offset, length, &out) !=
AMDUAT_FPS_OK) {
ok = false;
} else if (!hex_eq(output, out)) {
ok = false;
}
}
} else if (ok && strcmp(op, "concat") == 0) {
if (!has_inputs) {
ok = false;
} else {
amduat_octets_t in_octets[4];
for (size_t i = 0u; i < inputs_len; ++i) {
if (!hex_decode(inputs[i], &decoded, &decoded_len)) {
ok = false;
break;
}
in_octets[i] = amduat_octets(decoded, decoded_len);
}
if (ok) {
if (amduat_fps_concat(in_octets, inputs_len, &out) !=
AMDUAT_FPS_OK) {
ok = false;
} else if (!hex_eq(output, out)) {
ok = false;
}
}
for (size_t i = 0u; i < inputs_len; ++i) {
free((void *)in_octets[i].data);
}
}
} else if (ok && strcmp(op, "reverse") == 0) {
amduat_fps_reverse_level_t lvl = AMDUAT_FPS_REVERSE_BYTES;
if (!has_input || !has_level) {
ok = false;
} else if (strcmp(level, "bit") == 0) {
lvl = AMDUAT_FPS_REVERSE_BITS;
} else if (strcmp(level, "byte") == 0) {
lvl = AMDUAT_FPS_REVERSE_BYTES;
} else if (strcmp(level, "word") == 0) {
lvl = AMDUAT_FPS_REVERSE_WORDS;
} else if (strcmp(level, "long") == 0) {
lvl = AMDUAT_FPS_REVERSE_LONGS;
} else {
ok = false;
}
if (ok && !hex_decode(input, &decoded, &decoded_len)) {
ok = false;
}
if (ok) {
input_bytes = amduat_octets(decoded, decoded_len);
if (amduat_fps_reverse(input_bytes, lvl, &out) != AMDUAT_FPS_OK) {
ok = false;
} else if (!hex_eq(output, out)) {
ok = false;
}
}
} else if (ok && strcmp(op, "splice") == 0) {
if (!has_input || !has_insert || !has_offset) {
ok = false;
} else if (!hex_decode(input, &decoded, &decoded_len) ||
!hex_decode(insert, &insert_decoded, &insert_len)) {
ok = false;
} else {
input_bytes = amduat_octets(decoded, decoded_len);
insert_bytes = amduat_octets(insert_decoded, insert_len);
if (amduat_fps_splice(input_bytes, offset, insert_bytes, &out) !=
AMDUAT_FPS_OK) {
ok = false;
} else if (!hex_eq(output, out)) {
ok = false;
}
}
} else if (ok) {
ok = false;
}
amduat_octets_free(&out);
free((void *)output_bytes.data);
if (input_bytes.data != NULL) {
free((void *)input_bytes.data);
}
if (insert_bytes.data != NULL) {
free((void *)insert_bytes.data);
}
}
free(op);
free(input);
free(insert);
free(output);
free(cid_hex);
free(level);
for (size_t i = 0u; i < inputs_len; ++i) {
free(inputs[i]);
}
if (!ok) {
return false;
}
if (!cursor_skip_ws(cur)) {
return false;
}
if (cur->offset < cur->len && cur->data[cur->offset] == ',') {
cur->offset++;
continue;
}
if (cur->offset < cur->len && cur->data[cur->offset] == ']') {
cur->offset++;
return true;
}
}
}
static bool parse_manifest(uint8_t *bytes, size_t len) {
cursor_t cur;
char *field = NULL;
char *schema = NULL;
bool ok = false;
cur.data = bytes;
cur.len = len;
cur.offset = 0u;
if (!cursor_skip_ws(&cur) || cur.offset >= cur.len ||
cur.data[cur.offset] != '{') {
return false;
}
cur.offset++;
while (true) {
if (!cursor_read_string(&cur, &field)) {
break;
}
if (!cursor_skip_ws(&cur) || cur.offset >= cur.len ||
cur.data[cur.offset] != ':') {
break;
}
cur.offset++;
if (strcmp(field, "schema") == 0) {
if (!cursor_read_string(&cur, &schema)) {
break;
}
} else if (strcmp(field, "cases") == 0) {
if (!parse_manifest_cases(&cur)) {
break;
}
} else {
break;
}
free(field);
field = NULL;
if (!cursor_skip_ws(&cur)) {
break;
}
if (cur.offset < cur.len && cur.data[cur.offset] == ',') {
cur.offset++;
continue;
}
if (cur.offset < cur.len && cur.data[cur.offset] == '}') {
cur.offset++;
break;
}
}
if (schema != NULL &&
strcmp(schema, "amduat.conformance.fps.v0") == 0) {
ok = true;
}
free(field);
free(schema);
return ok;
}
int main(void) {
char path[512];
uint8_t *bytes = NULL;
size_t len = 0u;
int exit_code = 1;
if (!build_fixture_path(path, sizeof(path))) {
fprintf(stderr, "fixture path build failed\n");
return exit_code;
}
if (!read_file(path, &bytes, &len)) {
fprintf(stderr, "fixture read failed: %s\n", path);
return exit_code;
}
if (!parse_manifest(bytes, len)) {
fprintf(stderr, "fixture parse failed\n");
goto cleanup;
}
exit_code = 0;
cleanup:
free(bytes);
return exit_code;
}