Add Minio adapter.

This commit is contained in:
Carl Niklas Rydberg 2026-02-21 18:04:15 +01:00
parent 1f4b8ff5bc
commit 7e3e302988
13 changed files with 3826 additions and 88 deletions

View file

@ -157,6 +157,11 @@ set(AMDUAT_ASL_STORE_INDEX_FS_SRCS
src/adapters/asl_store_index_fs/asl_store_index_fs_layout.c src/adapters/asl_store_index_fs/asl_store_index_fs_layout.c
) )
set(AMDUAT_ASL_STORE_MINIO_SRCS
src/adapters/asl_store_minio/asl_store_minio.c
src/adapters/asl_store_minio/s3_sigv4.c
)
set(AMDUAT_ASL_DERIVATION_INDEX_FS_SRCS set(AMDUAT_ASL_DERIVATION_INDEX_FS_SRCS
src/adapters/asl_derivation_index_fs/asl_derivation_index_fs.c src/adapters/asl_derivation_index_fs/asl_derivation_index_fs.c
) )
@ -213,6 +218,10 @@ amduat_add_lib(asl_store_index_fs SRCS ${AMDUAT_ASL_STORE_INDEX_FS_SRCS})
amduat_link(asl_store_index_fs amduat_asl amduat_enc amduat_hash_asl1 amduat_util) amduat_link(asl_store_index_fs amduat_asl amduat_enc amduat_hash_asl1 amduat_util)
target_compile_definitions(amduat_asl_store_index_fs_obj PRIVATE _POSIX_C_SOURCE=200809L) target_compile_definitions(amduat_asl_store_index_fs_obj PRIVATE _POSIX_C_SOURCE=200809L)
amduat_add_lib(asl_store_minio SRCS ${AMDUAT_ASL_STORE_MINIO_SRCS})
amduat_link(asl_store_minio amduat_asl amduat_enc amduat_hash_asl1 amduat_util)
target_compile_definitions(amduat_asl_store_minio_obj PRIVATE _POSIX_C_SOURCE=200809L)
amduat_add_lib(asl_derivation_index_fs SRCS ${AMDUAT_ASL_DERIVATION_INDEX_FS_SRCS}) amduat_add_lib(asl_derivation_index_fs SRCS ${AMDUAT_ASL_DERIVATION_INDEX_FS_SRCS})
amduat_link(asl_derivation_index_fs amduat_asl amduat_enc amduat_hash_asl1 amduat_util) amduat_link(asl_derivation_index_fs amduat_asl amduat_enc amduat_hash_asl1 amduat_util)
target_compile_definitions(amduat_asl_derivation_index_fs_obj PRIVATE _POSIX_C_SOURCE=200809L) target_compile_definitions(amduat_asl_derivation_index_fs_obj PRIVATE _POSIX_C_SOURCE=200809L)
@ -318,6 +327,7 @@ target_include_directories(amduat_conformance_run
) )
target_link_libraries(amduat_conformance_run target_link_libraries(amduat_conformance_run
PRIVATE amduat_asl_store_fs amduat_asl_store_index_fs PRIVATE amduat_asl_store_fs amduat_asl_store_index_fs
amduat_asl_store_minio
amduat_tgk_store_mem amduat_tgk_store_fs amduat_tgk_store_asl_index_fs amduat_tgk_store_mem amduat_tgk_store_fs amduat_tgk_store_asl_index_fs
amduat_tgk amduat_asl amduat_enc amduat_hash_asl1 amduat_util amduat_tgk amduat_asl amduat_enc amduat_hash_asl1 amduat_util
amduat_format amduat_format
@ -598,6 +608,7 @@ target_compile_definitions(amduat_test_asl_store_conformance
) )
target_link_libraries(amduat_test_asl_store_conformance target_link_libraries(amduat_test_asl_store_conformance
PRIVATE amduat_asl_store_fs amduat_asl_store_index_fs PRIVATE amduat_asl_store_fs amduat_asl_store_index_fs
amduat_asl_store_minio
amduat_asl amduat_enc amduat_hash_asl1 amduat_util amduat_asl amduat_enc amduat_hash_asl1 amduat_util
) )
add_test(NAME asl_store_conformance add_test(NAME asl_store_conformance

67
docs/minio-adapter.md Normal file
View file

@ -0,0 +1,67 @@
# MinIO ASL Store Adapter
This adapter provides an S3-compatible `ASL/1-STORE` backend for MinIO. It is
opt-in for conformance runs and does not require MinIO or any external services
at build time.
## Required environment
These are required when running conformance against the MinIO backend:
- `AMDUAT_MINIO_ENDPOINT` (e.g. `http://localhost:9000`)
- `AMDUAT_MINIO_BUCKET` (e.g. `amduat`)
- `AMDUAT_MINIO_ACCESS_KEY`
- `AMDUAT_MINIO_SECRET_KEY`
Optional configuration:
- `AMDUAT_MINIO_REGION` (default: `us-east-1`)
- `AMDUAT_MINIO_PREFIX` (default: `asl`)
- `AMDUAT_MINIO_FORCE_PATH_STYLE` (default: `1`)
- `AMDUAT_MINIO_TLS_VERIFY` (default: `1`)
- `AMDUAT_MINIO_TIMEOUT_MS` (default: `30000`)
- `AMDUAT_MINIO_RETRY_MAX` (default: `3`)
## Conformance runs
MinIO is skipped unless explicitly enabled.
Example (opt-in conformance run):
```sh
AMDUAT_CONFORMANCE_ENABLE_MINIO=1 \
AMDUAT_MINIO_ENDPOINT=http://localhost:9000 \
AMDUAT_MINIO_BUCKET=amduat \
AMDUAT_MINIO_ACCESS_KEY=minioadmin \
AMDUAT_MINIO_SECRET_KEY=minioadmin \
./build/amduat_conformance_run --suite asl --backend minio --workdir /tmp/amduat_conformance
```
If required environment variables are missing, the backend is reported as
`SKIPPED` with the missing variable names.
## Key layout
Objects are stored under a sharded key derived from the canonical reference
hex:
```
<prefix>/<r0r1>/<r2r3>/<full_reference_hex>
```
## Error mapping
| HTTP/S3 response | Canonical taxonomy | Store error |
| --- | --- | --- |
| 404 | NotFound | `AMDUAT_ASL_STORE_ERR_NOT_FOUND` |
| 401, 403 | PermissionDenied | `AMDUAT_ASL_STORE_ERR_IO` |
| 408, 429 | TransientIO | `AMDUAT_ASL_STORE_ERR_IO` |
| 5xx | TransientIO | `AMDUAT_ASL_STORE_ERR_IO` |
| 4xx (other) | InvalidArtifact | `AMDUAT_ASL_STORE_ERR_INTEGRITY` |
| Network/TLS/parse errors | InternalError/TransientIO | `AMDUAT_ASL_STORE_ERR_IO` |
## Limitations
- HTTPS is not supported yet (HTTP-only). Use `http://` endpoints for now.
- Multipart uploads, streaming artifacts, and very large objects are not
implemented in Phase 0.

View file

@ -0,0 +1,67 @@
#ifndef AMDUAT_ASL_STORE_MINIO_H
#define AMDUAT_ASL_STORE_MINIO_H
#include "amduat/asl/store.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
enum {
AMDUAT_ASL_STORE_MINIO_ENDPOINT_MAX = 1024,
AMDUAT_ASL_STORE_MINIO_HOST_MAX = 256,
AMDUAT_ASL_STORE_MINIO_BUCKET_MAX = 256,
AMDUAT_ASL_STORE_MINIO_ACCESS_KEY_MAX = 256,
AMDUAT_ASL_STORE_MINIO_SECRET_KEY_MAX = 256,
AMDUAT_ASL_STORE_MINIO_REGION_MAX = 64,
AMDUAT_ASL_STORE_MINIO_PREFIX_MAX = 128
};
typedef struct {
amduat_asl_store_config_t store_config;
const char *endpoint;
const char *bucket;
const char *access_key;
const char *secret_key;
const char *region;
const char *prefix;
bool force_path_style;
bool tls_verify;
uint32_t timeout_ms;
uint32_t retry_max;
} amduat_asl_store_minio_config_t;
typedef struct {
amduat_asl_store_config_t store_config;
char endpoint[AMDUAT_ASL_STORE_MINIO_ENDPOINT_MAX];
char host[AMDUAT_ASL_STORE_MINIO_HOST_MAX];
uint16_t port;
bool use_tls;
char bucket[AMDUAT_ASL_STORE_MINIO_BUCKET_MAX];
char access_key[AMDUAT_ASL_STORE_MINIO_ACCESS_KEY_MAX];
char secret_key[AMDUAT_ASL_STORE_MINIO_SECRET_KEY_MAX];
char region[AMDUAT_ASL_STORE_MINIO_REGION_MAX];
char prefix[AMDUAT_ASL_STORE_MINIO_PREFIX_MAX];
bool force_path_style;
bool tls_verify;
uint32_t timeout_ms;
uint32_t retry_max;
} amduat_asl_store_minio_t;
void amduat_asl_store_minio_config_defaults(
amduat_asl_store_minio_config_t *config);
bool amduat_asl_store_minio_init(amduat_asl_store_minio_t *minio,
amduat_asl_store_minio_config_t config);
amduat_asl_store_ops_t amduat_asl_store_minio_ops(void);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* AMDUAT_ASL_STORE_MINIO_H */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,288 @@
#include "s3_sigv4.h"
#include "amduat/asl/core.h"
#include "amduat/hash/asl1.h"
#include "amduat/util/hex.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
enum { AMDUAT_S3_SIGV4_SHA256_LEN = 32u, AMDUAT_S3_SIGV4_BLOCK_LEN = 64u };
static bool sha256_digest(const uint8_t *data, size_t len, uint8_t out[32]) {
amduat_octets_t input;
input.data = data;
input.len = len;
return amduat_hash_asl1_digest(AMDUAT_HASH_ASL1_ID_SHA256, input, out, 32u);
}
static bool sha256_hex(const uint8_t *data, size_t len, char out_hex[65]) {
uint8_t digest[AMDUAT_S3_SIGV4_SHA256_LEN];
if (!sha256_digest(data, len, digest)) {
return false;
}
if (!amduat_hex_encode_lower(digest, sizeof(digest), out_hex, 65u)) {
return false;
}
out_hex[64] = '\0';
return true;
}
static bool hmac_sha256(const uint8_t *key,
size_t key_len,
const uint8_t *msg,
size_t msg_len,
uint8_t out[32]) {
uint8_t key_block[AMDUAT_S3_SIGV4_BLOCK_LEN];
uint8_t ipad[AMDUAT_S3_SIGV4_BLOCK_LEN];
uint8_t opad[AMDUAT_S3_SIGV4_BLOCK_LEN];
uint8_t inner_hash[AMDUAT_S3_SIGV4_SHA256_LEN];
size_t i;
amduat_hash_asl1_stream_t stream;
memset(key_block, 0, sizeof(key_block));
if (key_len > sizeof(key_block)) {
if (!sha256_digest(key, key_len, key_block)) {
return false;
}
key_len = AMDUAT_S3_SIGV4_SHA256_LEN;
} else if (key_len > 0 && key != NULL) {
memcpy(key_block, key, key_len);
}
for (i = 0; i < sizeof(key_block); ++i) {
ipad[i] = (uint8_t)(key_block[i] ^ 0x36u);
opad[i] = (uint8_t)(key_block[i] ^ 0x5cu);
}
if (!amduat_hash_asl1_stream_init(AMDUAT_HASH_ASL1_ID_SHA256, &stream)) {
return false;
}
if (!amduat_hash_asl1_stream_update(
&stream, amduat_octets(ipad, sizeof(ipad))) ||
(msg_len > 0 &&
!amduat_hash_asl1_stream_update(
&stream, amduat_octets(msg, msg_len))) ||
!amduat_hash_asl1_stream_final(&stream, inner_hash, sizeof(inner_hash))) {
amduat_hash_asl1_stream_destroy(&stream);
return false;
}
amduat_hash_asl1_stream_destroy(&stream);
if (!amduat_hash_asl1_stream_init(AMDUAT_HASH_ASL1_ID_SHA256, &stream)) {
return false;
}
if (!amduat_hash_asl1_stream_update(
&stream, amduat_octets(opad, sizeof(opad))) ||
!amduat_hash_asl1_stream_update(
&stream, amduat_octets(inner_hash, sizeof(inner_hash))) ||
!amduat_hash_asl1_stream_final(&stream, out, AMDUAT_S3_SIGV4_SHA256_LEN)) {
amduat_hash_asl1_stream_destroy(&stream);
return false;
}
amduat_hash_asl1_stream_destroy(&stream);
return true;
}
bool amduat_s3_sigv4_build_authorization(const char *method,
const char *canonical_uri,
const char *canonical_query,
const char *host,
const char *payload_hash_hex,
const char *access_key,
const char *secret_key,
const char *region,
const char *amz_date,
char **out_authorization) {
const char *signed_headers = "host;x-amz-content-sha256;x-amz-date";
char date_stamp[9];
char *canonical_request = NULL;
char *string_to_sign = NULL;
char canonical_request_hash[65];
uint8_t k_date[AMDUAT_S3_SIGV4_SHA256_LEN];
uint8_t k_region[AMDUAT_S3_SIGV4_SHA256_LEN];
uint8_t k_service[AMDUAT_S3_SIGV4_SHA256_LEN];
uint8_t k_signing[AMDUAT_S3_SIGV4_SHA256_LEN];
uint8_t signature_bytes[AMDUAT_S3_SIGV4_SHA256_LEN];
char signature_hex[65];
char *auth_header = NULL;
size_t auth_len;
int rc;
if (out_authorization != NULL) {
*out_authorization = NULL;
}
if (method == NULL || canonical_uri == NULL || canonical_query == NULL ||
host == NULL || payload_hash_hex == NULL || access_key == NULL ||
secret_key == NULL || region == NULL || amz_date == NULL ||
out_authorization == NULL) {
return false;
}
if (strlen(amz_date) < 8u) {
return false;
}
memcpy(date_stamp, amz_date, 8u);
date_stamp[8] = '\0';
rc = snprintf(NULL,
0,
"%s\n%s\n%s\n"
"host:%s\n"
"x-amz-content-sha256:%s\n"
"x-amz-date:%s\n\n"
"%s\n"
"%s",
method,
canonical_uri,
canonical_query,
host,
payload_hash_hex,
amz_date,
signed_headers,
payload_hash_hex);
if (rc <= 0) {
return false;
}
canonical_request = (char *)malloc((size_t)rc + 1u);
if (canonical_request == NULL) {
return false;
}
snprintf(canonical_request,
(size_t)rc + 1u,
"%s\n%s\n%s\n"
"host:%s\n"
"x-amz-content-sha256:%s\n"
"x-amz-date:%s\n\n"
"%s\n"
"%s",
method,
canonical_uri,
canonical_query,
host,
payload_hash_hex,
amz_date,
signed_headers,
payload_hash_hex);
if (!sha256_hex((const uint8_t *)canonical_request,
strlen(canonical_request),
canonical_request_hash)) {
free(canonical_request);
return false;
}
rc = snprintf(NULL,
0,
"AWS4-HMAC-SHA256\n%s\n%s/%s/s3/aws4_request\n%s",
amz_date,
date_stamp,
region,
canonical_request_hash);
if (rc <= 0) {
free(canonical_request);
return false;
}
string_to_sign = (char *)malloc((size_t)rc + 1u);
if (string_to_sign == NULL) {
free(canonical_request);
return false;
}
snprintf(string_to_sign,
(size_t)rc + 1u,
"AWS4-HMAC-SHA256\n%s\n%s/%s/s3/aws4_request\n%s",
amz_date,
date_stamp,
region,
canonical_request_hash);
{
size_t secret_len = strlen(secret_key);
size_t key_len = 4u + secret_len;
char *key_prefix = (char *)malloc(key_len + 1u);
if (key_prefix == NULL) {
free(canonical_request);
free(string_to_sign);
return false;
}
memcpy(key_prefix, "AWS4", 4u);
memcpy(key_prefix + 4u, secret_key, secret_len);
key_prefix[key_len] = '\0';
if (!hmac_sha256((const uint8_t *)key_prefix,
key_len,
(const uint8_t *)date_stamp,
strlen(date_stamp),
k_date) ||
!hmac_sha256(k_date,
sizeof(k_date),
(const uint8_t *)region,
strlen(region),
k_region) ||
!hmac_sha256(k_region,
sizeof(k_region),
(const uint8_t *)"s3",
2u,
k_service) ||
!hmac_sha256(k_service,
sizeof(k_service),
(const uint8_t *)"aws4_request",
12u,
k_signing) ||
!hmac_sha256(k_signing,
sizeof(k_signing),
(const uint8_t *)string_to_sign,
strlen(string_to_sign),
signature_bytes)) {
free(key_prefix);
free(canonical_request);
free(string_to_sign);
return false;
}
free(key_prefix);
}
if (!amduat_hex_encode_lower(signature_bytes,
sizeof(signature_bytes),
signature_hex,
sizeof(signature_hex))) {
free(canonical_request);
free(string_to_sign);
return false;
}
auth_len = (size_t)snprintf(NULL,
0,
"AWS4-HMAC-SHA256 Credential=%s/%s/%s/s3/aws4_request, "
"SignedHeaders=%s, Signature=%s",
access_key,
date_stamp,
region,
signed_headers,
signature_hex);
auth_header = (char *)malloc(auth_len + 1u);
if (auth_header == NULL) {
free(canonical_request);
free(string_to_sign);
return false;
}
snprintf(auth_header,
auth_len + 1u,
"AWS4-HMAC-SHA256 Credential=%s/%s/%s/s3/aws4_request, "
"SignedHeaders=%s, Signature=%s",
access_key,
date_stamp,
region,
signed_headers,
signature_hex);
free(canonical_request);
free(string_to_sign);
*out_authorization = auth_header;
return true;
}

View file

@ -0,0 +1,25 @@
#ifndef AMDUAT_ADAPTERS_ASL_STORE_MINIO_S3_SIGV4_H
#define AMDUAT_ADAPTERS_ASL_STORE_MINIO_S3_SIGV4_H
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
bool amduat_s3_sigv4_build_authorization(const char *method,
const char *canonical_uri,
const char *canonical_query,
const char *host,
const char *payload_hash_hex,
const char *access_key,
const char *secret_key,
const char *region,
const char *amz_date,
char **out_authorization);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* AMDUAT_ADAPTERS_ASL_STORE_MINIO_S3_SIGV4_H */

View file

@ -70,9 +70,13 @@ static int print_results(const char *suite,
} }
for (i = 0; i < results_len; ++i) { for (i = 0; i < results_len; ++i) {
const char *status = results[i].status == 0 ? "PASS" : "FAIL"; const char *status =
results[i].status == AMDUAT_CONFORMANCE_STATUS_PASS
? "PASS"
: (results[i].status == AMDUAT_CONFORMANCE_STATUS_SKIP ? "SKIPPED"
: "FAIL");
printf("%s backend=%s %s\n", suite, results[i].backend, status); printf("%s backend=%s %s\n", suite, results[i].backend, status);
if (results[i].status != 0) { if (results[i].status == AMDUAT_CONFORMANCE_STATUS_FAIL) {
rc = 1; rc = 1;
} }
} }

View file

@ -2,11 +2,15 @@
#include "amduat/asl/asl_store_fs.h" #include "amduat/asl/asl_store_fs.h"
#include "amduat/asl/asl_store_index_fs.h" #include "amduat/asl/asl_store_index_fs.h"
#include "amduat/asl/asl_store_minio.h"
#include "amduat/asl/core.h" #include "amduat/asl/core.h"
#include "amduat/asl/ref_text.h"
#include "amduat/asl/store.h" #include "amduat/asl/store.h"
#include "amduat/enc/asl1_core.h" #include "amduat/enc/asl1_core.h"
#include "amduat/enc/asl1_core_codec.h" #include "amduat/enc/asl1_core_codec.h"
#include "amduat/enc/asl_log.h"
#include "amduat/hash/asl1.h" #include "amduat/hash/asl1.h"
#include "amduat/asl/index_replay.h"
#include <ctype.h> #include <ctype.h>
#include <dirent.h> #include <dirent.h>
@ -41,6 +45,18 @@ typedef struct {
amduat_asl_store_index_fs_t fs; amduat_asl_store_index_fs_t fs;
} amduat_asl_index_fs_ctx_t; } amduat_asl_index_fs_ctx_t;
typedef struct {
amduat_asl_store_minio_t minio;
} amduat_asl_minio_ctx_t;
static bool conformance_env_truthy(const char *value) {
if (value == NULL) {
return false;
}
return strcmp(value, "1") == 0 || strcmp(value, "true") == 0 ||
strcmp(value, "yes") == 0;
}
static bool conformance_join_path(const char *base, static bool conformance_join_path(const char *base,
const char *segment, const char *segment,
char **out_path) { char **out_path) {
@ -82,6 +98,82 @@ static bool conformance_join_path(const char *base,
return true; return true;
} }
static bool conformance_append_list(char **list, const char *entry) {
size_t list_len;
size_t entry_len;
size_t next_len;
char *next;
if (list == NULL || entry == NULL) {
return false;
}
if (*list == NULL) {
*list = strdup(entry);
return *list != NULL;
}
list_len = strlen(*list);
entry_len = strlen(entry);
next_len = list_len + 2u + entry_len + 1u;
next = (char *)realloc(*list, next_len);
if (next == NULL) {
return false;
}
next[list_len] = ',';
next[list_len + 1u] = ' ';
memcpy(next + list_len + 2u, entry, entry_len);
next[next_len - 1u] = '\0';
*list = next;
return true;
}
static bool conformance_minio_missing_envs(char **out_missing) {
if (out_missing != NULL) {
*out_missing = NULL;
}
if (out_missing == NULL) {
return false;
}
{
const char *value = getenv("AMDUAT_MINIO_ENDPOINT");
if (value == NULL || value[0] == '\0') {
if (!conformance_append_list(out_missing, "AMDUAT_MINIO_ENDPOINT")) {
return false;
}
}
}
{
const char *value = getenv("AMDUAT_MINIO_BUCKET");
if (value == NULL || value[0] == '\0') {
if (!conformance_append_list(out_missing, "AMDUAT_MINIO_BUCKET")) {
return false;
}
}
}
{
const char *value = getenv("AMDUAT_MINIO_ACCESS_KEY");
if (value == NULL || value[0] == '\0') {
if (!conformance_append_list(out_missing, "AMDUAT_MINIO_ACCESS_KEY")) {
return false;
}
}
}
{
const char *value = getenv("AMDUAT_MINIO_SECRET_KEY");
if (value == NULL || value[0] == '\0') {
if (!conformance_append_list(out_missing, "AMDUAT_MINIO_SECRET_KEY")) {
return false;
}
}
}
return true;
}
static bool conformance_remove_tree(const char *path) { static bool conformance_remove_tree(const char *path) {
struct stat st; struct stat st;
DIR *dir; DIR *dir;
@ -536,6 +628,18 @@ static bool conformance_build_artifact(amduat_octets_t payload,
return true; return true;
} }
static void conformance_free_ref_hexes(char **refs, size_t refs_len) {
size_t i;
if (refs == NULL) {
return;
}
for (i = 0; i < refs_len; ++i) {
free(refs[i]);
}
free(refs);
}
static bool conformance_asl_is_not_found(amduat_asl_store_error_t err) { static bool conformance_asl_is_not_found(amduat_asl_store_error_t err) {
return err == AMDUAT_ASL_STORE_ERR_NOT_FOUND; return err == AMDUAT_ASL_STORE_ERR_NOT_FOUND;
} }
@ -543,7 +647,8 @@ static bool conformance_asl_is_not_found(amduat_asl_store_error_t err) {
static int conformance_test_put_get(amduat_asl_store_t *store, static int conformance_test_put_get(amduat_asl_store_t *store,
const amduat_conformance_fixture_t *fixtures, const amduat_conformance_fixture_t *fixtures,
size_t fixtures_len, size_t fixtures_len,
const char *fixture_root) { const char *fixture_root,
char **out_ref_hexes) {
size_t i; size_t i;
for (i = 0; i < fixtures_len; ++i) { for (i = 0; i < fixtures_len; ++i) {
@ -594,6 +699,18 @@ static int conformance_test_put_get(amduat_asl_store_t *store,
return 1; return 1;
} }
if (out_ref_hexes != NULL) {
char *hex = NULL;
if (!amduat_asl_ref_encode_hex(ref, &hex)) {
fprintf(stderr, "ref encode failed for fixture %s\n", fixtures[i].id);
amduat_reference_free(&ref);
amduat_artifact_free(&artifact);
amduat_artifact_free(&fetched);
return 1;
}
out_ref_hexes[i] = hex;
}
if (!amduat_artifact_eq(artifact, fetched)) { if (!amduat_artifact_eq(artifact, fetched)) {
fprintf(stderr, "payload mismatch for fixture %s\n", fixtures[i].id); fprintf(stderr, "payload mismatch for fixture %s\n", fixtures[i].id);
amduat_reference_free(&ref); amduat_reference_free(&ref);
@ -697,6 +814,88 @@ static int conformance_test_get_not_found(amduat_asl_store_t *store) {
return 0; return 0;
} }
static bool conformance_replay_state_equal(
const amduat_asl_replay_state_t *lhs,
const amduat_asl_replay_state_t *rhs) {
size_t i;
if (lhs->segments_len != rhs->segments_len ||
lhs->tombstones_len != rhs->tombstones_len ||
lhs->state.log_position != rhs->state.log_position) {
return false;
}
for (i = 0; i < lhs->segments_len; ++i) {
const amduat_asl_segment_seal_t *left = &lhs->segments[i];
const amduat_asl_segment_seal_t *right = &rhs->segments[i];
if (left->segment_id != right->segment_id ||
memcmp(left->segment_hash, right->segment_hash,
sizeof(left->segment_hash)) != 0) {
return false;
}
}
for (i = 0; i < lhs->tombstones_len; ++i) {
const amduat_asl_tombstone_entry_t *left = &lhs->tombstones[i];
const amduat_asl_tombstone_entry_t *right = &rhs->tombstones[i];
if (left->tombstone_logseq != right->tombstone_logseq ||
!amduat_reference_eq(left->ref, right->ref)) {
return false;
}
}
return true;
}
static int conformance_test_replay_determinism(amduat_asl_store_t *store) {
amduat_asl_log_record_t *records = NULL;
size_t record_count = 0u;
amduat_asl_replay_state_t first;
amduat_asl_replay_state_t second;
amduat_asl_store_error_t err;
amduat_asl_log_position_t log_position = 0u;
int rc = 1;
err = amduat_asl_log_scan(store, &records, &record_count);
if (err != AMDUAT_ASL_STORE_OK) {
fprintf(stderr, "log scan failed: %d\n", err);
return 1;
}
if (record_count == 0u) {
fprintf(stderr, "log scan returned no records\n");
amduat_enc_asl_log_free(records, record_count);
return 1;
}
log_position = records[record_count - 1u].logseq;
if (!amduat_asl_replay_init(&first) ||
!amduat_asl_replay_init(&second)) {
fprintf(stderr, "replay init failed\n");
goto cleanup;
}
if (!amduat_asl_replay_apply_log(records, record_count, log_position, &first) ||
!amduat_asl_replay_apply_log(records, record_count, log_position, &second)) {
fprintf(stderr, "replay apply failed\n");
goto cleanup_states;
}
if (!conformance_replay_state_equal(&first, &second)) {
fprintf(stderr, "replay determinism mismatch\n");
goto cleanup_states;
}
rc = 0;
cleanup_states:
amduat_asl_replay_free(&first);
amduat_asl_replay_free(&second);
cleanup:
amduat_enc_asl_log_free(records, record_count);
return rc;
}
static bool conformance_init_asl_store_fs(const char *root, static bool conformance_init_asl_store_fs(const char *root,
amduat_asl_store_t *out_store, amduat_asl_store_t *out_store,
void **out_ctx) { void **out_ctx) {
@ -757,6 +956,71 @@ static bool conformance_init_asl_store_index_fs(
return true; return true;
} }
static bool conformance_init_asl_store_minio(
const char *root,
amduat_asl_store_t *out_store,
void **out_ctx) {
amduat_asl_minio_ctx_t *ctx;
amduat_asl_store_minio_config_t config;
const char *timeout_env;
const char *retry_env;
const char *force_env;
const char *tls_env;
(void)root;
if (out_store == NULL || out_ctx == NULL) {
return false;
}
ctx = (amduat_asl_minio_ctx_t *)calloc(1, sizeof(*ctx));
if (ctx == NULL) {
return false;
}
amduat_asl_store_minio_config_defaults(&config);
config.endpoint = getenv("AMDUAT_MINIO_ENDPOINT");
config.bucket = getenv("AMDUAT_MINIO_BUCKET");
config.access_key = getenv("AMDUAT_MINIO_ACCESS_KEY");
config.secret_key = getenv("AMDUAT_MINIO_SECRET_KEY");
config.region = getenv("AMDUAT_MINIO_REGION") != NULL
? getenv("AMDUAT_MINIO_REGION")
: config.region;
config.prefix = getenv("AMDUAT_MINIO_PREFIX") != NULL
? getenv("AMDUAT_MINIO_PREFIX")
: config.prefix;
force_env = getenv("AMDUAT_MINIO_FORCE_PATH_STYLE");
if (force_env != NULL) {
config.force_path_style = conformance_env_truthy(force_env);
}
tls_env = getenv("AMDUAT_MINIO_TLS_VERIFY");
if (tls_env != NULL) {
config.tls_verify = conformance_env_truthy(tls_env);
}
timeout_env = getenv("AMDUAT_MINIO_TIMEOUT_MS");
if (timeout_env != NULL) {
config.timeout_ms = (uint32_t)strtoul(timeout_env, NULL, 10);
}
retry_env = getenv("AMDUAT_MINIO_RETRY_MAX");
if (retry_env != NULL) {
config.retry_max = (uint32_t)strtoul(retry_env, NULL, 10);
}
if (!amduat_asl_store_minio_init(&ctx->minio, config)) {
free(ctx);
return false;
}
amduat_asl_store_init(out_store, config.store_config,
amduat_asl_store_minio_ops(), &ctx->minio);
*out_ctx = ctx;
return true;
}
static void conformance_free_ctx(void *ctx) { static void conformance_free_ctx(void *ctx) {
free(ctx); free(ctx);
} }
@ -766,7 +1030,8 @@ static int conformance_run_backend(
const char *workdir, const char *workdir,
const amduat_conformance_fixture_t *fixtures, const amduat_conformance_fixture_t *fixtures,
size_t fixtures_len, size_t fixtures_len,
const char *fixture_root) { const char *fixture_root,
char **out_ref_hexes) {
amduat_asl_store_t store; amduat_asl_store_t store;
void *ctx = NULL; void *ctx = NULL;
int rc = 1; int rc = 1;
@ -778,7 +1043,8 @@ static int conformance_run_backend(
return 1; return 1;
} }
if (conformance_test_put_get(&store, fixtures, fixtures_len, fixture_root) != 0 || if (conformance_test_put_get(&store, fixtures, fixtures_len, fixture_root,
out_ref_hexes) != 0 ||
conformance_test_put_idempotent(&store, &fixtures[0], fixture_root) != 0 || conformance_test_put_idempotent(&store, &fixtures[0], fixture_root) != 0 ||
conformance_test_get_not_found(&store) != 0) { conformance_test_get_not_found(&store) != 0) {
rc = 1; rc = 1;
@ -786,6 +1052,12 @@ static int conformance_run_backend(
rc = 0; rc = 0;
} }
if (rc == 0 && strcmp(backend->name, "asl_store_index_fs") == 0) {
if (conformance_test_replay_determinism(&store) != 0) {
rc = 1;
}
}
if (backend->cleanup != NULL) { if (backend->cleanup != NULL) {
backend->cleanup(ctx); backend->cleanup(ctx);
} }
@ -804,6 +1076,7 @@ int amduat_asl_store_conformance_run(
{"asl_store_fs", false, conformance_init_asl_store_fs, conformance_free_ctx}, {"asl_store_fs", false, conformance_init_asl_store_fs, conformance_free_ctx},
{"asl_store_index_fs", false, conformance_init_asl_store_index_fs, {"asl_store_index_fs", false, conformance_init_asl_store_index_fs,
conformance_free_ctx}, conformance_free_ctx},
{"minio", false, conformance_init_asl_store_minio, conformance_free_ctx},
}; };
char *fixture_dir = NULL; char *fixture_dir = NULL;
char *manifest_path = NULL; char *manifest_path = NULL;
@ -813,6 +1086,8 @@ int amduat_asl_store_conformance_run(
size_t i; size_t i;
size_t results_len = 0u; size_t results_len = 0u;
amduat_conformance_backend_result_t *results = NULL; amduat_conformance_backend_result_t *results = NULL;
char **baseline_refs = NULL;
size_t baseline_len = 0u;
if (out_results == NULL || out_len == NULL) { if (out_results == NULL || out_len == NULL) {
return 1; return 1;
@ -855,10 +1130,97 @@ int amduat_asl_store_conformance_run(
if (backend->optional && !include_optional) { if (backend->optional && !include_optional) {
continue; continue;
} }
if (strcmp(backend->name, "minio") == 0) {
const char *enable_env = getenv("AMDUAT_CONFORMANCE_ENABLE_MINIO");
char *missing = NULL;
const char *endpoint_env = getenv("AMDUAT_MINIO_ENDPOINT");
if (!conformance_env_truthy(enable_env)) {
results = (amduat_conformance_backend_result_t *)realloc(
results, (results_len + 1u) * sizeof(*results));
if (results == NULL) {
conformance_fixture_list_free(fixtures, fixtures_len);
free(fixture_dir);
free(manifest_path);
free(artifact_dir);
return 1;
}
results[results_len].backend = backend->name;
results[results_len].status = AMDUAT_CONFORMANCE_STATUS_SKIP;
results_len++;
if (verbose) {
fprintf(stdout,
"asl minio SKIPPED: set AMDUAT_CONFORMANCE_ENABLE_MINIO=1\n");
}
continue;
}
if (!conformance_minio_missing_envs(&missing)) {
conformance_fixture_list_free(fixtures, fixtures_len);
free(fixture_dir);
free(manifest_path);
free(artifact_dir);
free(results);
return 1;
}
if (missing != NULL) {
results = (amduat_conformance_backend_result_t *)realloc(
results, (results_len + 1u) * sizeof(*results));
if (results == NULL) {
free(missing);
conformance_fixture_list_free(fixtures, fixtures_len);
free(fixture_dir);
free(manifest_path);
free(artifact_dir);
return 1;
}
results[results_len].backend = backend->name;
results[results_len].status = AMDUAT_CONFORMANCE_STATUS_SKIP;
results_len++;
if (verbose) {
fprintf(stdout, "asl minio SKIPPED: missing %s\n", missing);
}
free(missing);
continue;
}
if (endpoint_env != NULL && strncmp(endpoint_env, "https://", 8u) == 0) {
results = (amduat_conformance_backend_result_t *)realloc(
results, (results_len + 1u) * sizeof(*results));
if (results == NULL) {
conformance_fixture_list_free(fixtures, fixtures_len);
free(fixture_dir);
free(manifest_path);
free(artifact_dir);
return 1;
}
results[results_len].backend = backend->name;
results[results_len].status = AMDUAT_CONFORMANCE_STATUS_SKIP;
results_len++;
if (verbose) {
fprintf(stdout,
"asl minio SKIPPED: https endpoints not supported\n");
}
continue;
}
}
char **ref_hexes =
(char **)calloc(fixtures_len, sizeof(char *));
if (ref_hexes == NULL) {
fprintf(stderr, "out of memory\n");
conformance_fixture_list_free(fixtures, fixtures_len);
free(fixture_dir);
free(manifest_path);
free(artifact_dir);
free(results);
return 1;
}
if (!conformance_prepare_workdir(workdir, backend->name, "asl", if (!conformance_prepare_workdir(workdir, backend->name, "asl",
&backend_workdir)) { &backend_workdir)) {
fprintf(stderr, "workdir prep failed for %s\n", backend->name); fprintf(stderr, "workdir prep failed for %s\n", backend->name);
conformance_free_ref_hexes(ref_hexes, fixtures_len);
conformance_fixture_list_free(fixtures, fixtures_len); conformance_fixture_list_free(fixtures, fixtures_len);
free(fixture_dir); free(fixture_dir);
free(manifest_path); free(manifest_path);
@ -881,11 +1243,44 @@ int amduat_asl_store_conformance_run(
results[results_len].backend = backend->name; results[results_len].backend = backend->name;
results[results_len].status = results[results_len].status =
conformance_run_backend(backend, backend_workdir, fixtures, conformance_run_backend(backend, backend_workdir, fixtures,
fixtures_len, artifact_dir); fixtures_len, artifact_dir, ref_hexes);
if (verbose) { if (verbose) {
fprintf(stdout, "asl %s %s\n", backend->name, const char *status =
results[results_len].status == 0 ? "PASS" : "FAIL"); results[results_len].status == AMDUAT_CONFORMANCE_STATUS_PASS
? "PASS"
: (results[results_len].status == AMDUAT_CONFORMANCE_STATUS_SKIP
? "SKIPPED"
: "FAIL");
fprintf(stdout, "asl %s %s\n", backend->name, status);
} }
if (results[results_len].status ==
AMDUAT_CONFORMANCE_STATUS_PASS) {
if (baseline_refs == NULL) {
baseline_refs = ref_hexes;
baseline_len = fixtures_len;
ref_hexes = NULL;
} else {
size_t j;
if (baseline_len != fixtures_len) {
fprintf(stderr, "cross-backend ref length mismatch\n");
results[results_len].status = 1;
} else {
for (j = 0; j < fixtures_len; ++j) {
if (baseline_refs[j] == NULL || ref_hexes[j] == NULL ||
strcmp(baseline_refs[j], ref_hexes[j]) != 0) {
fprintf(stderr,
"cross-backend ref mismatch for fixture %s\n",
fixtures[j].id);
results[results_len].status = 1;
break;
}
}
}
}
}
conformance_free_ref_hexes(ref_hexes, fixtures_len);
results_len++; results_len++;
free(backend_workdir); free(backend_workdir);
} }
@ -894,12 +1289,13 @@ int amduat_asl_store_conformance_run(
free(fixture_dir); free(fixture_dir);
free(manifest_path); free(manifest_path);
free(artifact_dir); free(artifact_dir);
conformance_free_ref_hexes(baseline_refs, baseline_len);
*out_results = results; *out_results = results;
*out_len = results_len; *out_len = results_len;
for (i = 0; i < results_len; ++i) { for (i = 0; i < results_len; ++i) {
if (results[i].status != 0) { if (results[i].status == AMDUAT_CONFORMANCE_STATUS_FAIL) {
return 1; return 1;
} }
} }

View file

@ -76,7 +76,7 @@ int main(void) {
} }
for (i = 0; i < results_len; ++i) { for (i = 0; i < results_len; ++i) {
if (results[i].status != 0) { if (results[i].status == AMDUAT_CONFORMANCE_STATUS_FAIL) {
rc = 1; rc = 1;
} }
} }

View file

@ -12,6 +12,12 @@ typedef struct {
int status; int status;
} amduat_conformance_backend_result_t; } amduat_conformance_backend_result_t;
enum {
AMDUAT_CONFORMANCE_STATUS_PASS = 0,
AMDUAT_CONFORMANCE_STATUS_FAIL = 1,
AMDUAT_CONFORMANCE_STATUS_SKIP = 2
};
#ifdef __cplusplus #ifdef __cplusplus
} /* extern "C" */ } /* extern "C" */
#endif #endif

View file

@ -76,7 +76,7 @@ int main(void) {
} }
for (i = 0; i < results_len; ++i) { for (i = 0; i < results_len; ++i) {
if (results[i].status != 0) { if (results[i].status == AMDUAT_CONFORMANCE_STATUS_FAIL) {
rc = 1; rc = 1;
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,26 @@
{
"schema": "amduat.conformance.tgk_edges.v0",
"edges": [
{
"id": "e1",
"type": 16,
"from": ["00010a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a"],
"to": ["00010b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"],
"payload": "00011a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a"
},
{
"id": "e2",
"type": 16,
"from": ["00010a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a"],
"to": ["00010c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c"],
"payload": "00011b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b"
},
{
"id": "e3",
"type": 32,
"from": ["00010b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"],
"to": ["00010a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a"],
"payload": "00011c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c"
}
]
}