#include "amduat/fps.h" #include #include #include #include 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; }