zupt/sdk/tests/test_sdk_roundtrip.c
Cristian Cezar Moisés e5f5d32aab v2.2.2
2026-05-01 09:58:47 -03:00

412 lines
15 KiB
C

/*
* libzuptsdk roundtrip test
* Copyright (c) 2026 Cristian Cezar Moisés
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* Exercises every public SDK function with a byte-exact verification.
* Returns 0 on success; non-zero on any failure.
*/
#define _DEFAULT_SOURCE 1
#include <zuptsdk.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static int g_pass = 0, g_fail = 0;
#define TEST(name) do { \
fprintf(stderr, " %-60s", name); \
fflush(stderr); \
} while (0)
#define PASS() do { \
fprintf(stderr, "PASS\n"); \
g_pass++; \
} while (0)
#define FAIL(reason) do { \
fprintf(stderr, "FAIL: %s\n", reason); \
if (zuptsdk_last_error_detail()[0]) \
fprintf(stderr, " detail: %s\n", zuptsdk_last_error_detail()); \
g_fail++; \
} while (0)
#define CHECK(rc, msg) do { \
if ((rc) != ZUPTSDK_OK) { FAIL(msg); return; } \
} while (0)
static const uint8_t TEST_DATA[] =
"Post-quantum backup test data for the libzuptsdk roundtrip suite. "
"This payload is repeated to ensure compression actually does something. "
"Lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod. "
"Lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod. "
"Lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod. "
"Lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod. "
"Lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod. "
"End of test data.\n";
static void test_version(void) {
TEST("version_string returns non-NULL");
const char *v = zuptsdk_version_string();
if (!v || strlen(v) == 0) { FAIL("empty"); return; }
if (strcmp(v, ZUPTSDK_VERSION_STRING) != 0) { FAIL("mismatch"); return; }
PASS();
TEST("version_check accepts current version");
int rc = zuptsdk_version_check(ZUPTSDK_VERSION_MAJOR,
ZUPTSDK_VERSION_MINOR,
ZUPTSDK_VERSION_PATCH);
CHECK(rc, "rejected own version");
PASS();
TEST("version_check rejects future version");
rc = zuptsdk_version_check(99, 99, 99);
if (rc != ZUPTSDK_ERR_VERSION_MISMATCH) { FAIL("should reject"); return; }
PASS();
}
static void test_strerror(void) {
TEST("strerror handles every error code");
for (int e = 0; e >= -100; e--) {
const char *s = zuptsdk_strerror(e);
if (!s || !*s) { FAIL("empty"); return; }
}
PASS();
}
static void test_context(void) {
TEST("ctx_create / ctx_destroy");
zuptsdk_ctx_t *c = NULL;
CHECK(zuptsdk_ctx_create(&c), "create failed");
if (!c) { FAIL("ctx is NULL"); return; }
zuptsdk_ctx_destroy(c);
zuptsdk_ctx_destroy(NULL); /* should not crash */
PASS();
TEST("ctx_set_threads validates range");
CHECK(zuptsdk_ctx_create(&c), "create");
int rc = zuptsdk_ctx_set_threads(c, 0); /* auto */
CHECK(rc, "auto");
rc = zuptsdk_ctx_set_threads(c, 4); /* normal */
CHECK(rc, "4");
rc = zuptsdk_ctx_set_threads(c, -1); /* invalid */
if (rc != ZUPTSDK_ERR_INVALID_ARG) { FAIL("should reject -1"); zuptsdk_ctx_destroy(c); return; }
rc = zuptsdk_ctx_set_threads(c, 999); /* invalid */
if (rc != ZUPTSDK_ERR_INVALID_ARG) { FAIL("should reject 999"); zuptsdk_ctx_destroy(c); return; }
zuptsdk_ctx_destroy(c);
PASS();
}
static void test_options(void) {
TEST("options builder full sequence");
zuptsdk_options_t *o = NULL;
CHECK(zuptsdk_options_create(&o), "create");
CHECK(zuptsdk_options_set_codec(o, ZUPTSDK_CODEC_AUTO), "codec auto");
CHECK(zuptsdk_options_set_codec(o, ZUPTSDK_CODEC_VAPTVUPT), "codec vv");
CHECK(zuptsdk_options_set_level(o, 7), "level 7");
CHECK(zuptsdk_options_set_dedup(o, 1), "dedup");
CHECK(zuptsdk_options_set_solid(o, 0), "solid");
CHECK(zuptsdk_options_set_max_decompressed(o, 1024 * 1024), "max");
int rc = zuptsdk_options_set_level(o, 99);
if (rc != ZUPTSDK_ERR_INVALID_ARG) { FAIL("level 99 should fail"); zuptsdk_options_destroy(o); return; }
zuptsdk_options_destroy(o);
PASS();
}
static void test_secure_buf(void) {
TEST("secure_buf create/get/destroy");
zuptsdk_secure_buf_t *b = NULL;
CHECK(zuptsdk_secure_buf_create(64, &b), "create");
uint8_t *data = NULL; size_t sz = 0;
CHECK(zuptsdk_secure_buf_get(b, &data, &sz), "get");
if (sz != 64) { FAIL("size wrong"); zuptsdk_secure_buf_destroy(b); return; }
memset(data, 0xAA, 64);
if (data[0] != 0xAA || data[63] != 0xAA) { FAIL("write/read"); zuptsdk_secure_buf_destroy(b); return; }
zuptsdk_secure_buf_destroy(b);
PASS();
TEST("secure_buf_from_data copies");
const uint8_t src[] = "secret password value!";
CHECK(zuptsdk_secure_buf_from_data(src, sizeof(src) - 1, &b), "from_data");
CHECK(zuptsdk_secure_buf_get(b, &data, &sz), "get");
if (sz != sizeof(src) - 1) { FAIL("size"); zuptsdk_secure_buf_destroy(b); return; }
if (memcmp(data, src, sz) != 0) { FAIL("content"); zuptsdk_secure_buf_destroy(b); return; }
zuptsdk_secure_buf_destroy(b);
PASS();
}
static int byteexact(const uint8_t *a, size_t na, const uint8_t *b, size_t nb) {
return (na == nb) && (memcmp(a, b, na) == 0);
}
static void test_compress_buffer_plain(void) {
TEST("compress_buffer + extract_buffer (plain)");
zuptsdk_ctx_t *ctx = NULL;
CHECK(zuptsdk_ctx_create(&ctx), "ctx");
zuptsdk_options_t *opts = NULL;
CHECK(zuptsdk_options_create(&opts), "opts");
zuptsdk_options_set_codec(opts, ZUPTSDK_CODEC_AUTO);
zuptsdk_options_set_level(opts, 5);
uint8_t *arc = NULL; size_t arc_sz = 0;
int rc = zuptsdk_compress_buffer(ctx, opts, "test.txt",
TEST_DATA, sizeof(TEST_DATA) - 1,
NULL, NULL, &arc, &arc_sz);
if (rc != ZUPTSDK_OK) { FAIL("compress"); zuptsdk_options_destroy(opts); zuptsdk_ctx_destroy(ctx); return; }
uint8_t *out = NULL; size_t out_sz = 0;
rc = zuptsdk_extract_buffer(ctx, arc, arc_sz, NULL, NULL, &out, &out_sz);
zuptsdk_free(arc);
if (rc != ZUPTSDK_OK) { FAIL("extract"); zuptsdk_options_destroy(opts); zuptsdk_ctx_destroy(ctx); return; }
if (!byteexact(out, out_sz, TEST_DATA, sizeof(TEST_DATA) - 1)) {
FAIL("byte mismatch");
zuptsdk_free(out);
zuptsdk_options_destroy(opts);
zuptsdk_ctx_destroy(ctx);
return;
}
zuptsdk_free(out);
zuptsdk_options_destroy(opts);
zuptsdk_ctx_destroy(ctx);
PASS();
}
static void test_compress_buffer_password(void) {
TEST("compress_buffer + extract_buffer (password)");
zuptsdk_ctx_t *ctx = NULL;
CHECK(zuptsdk_ctx_create(&ctx), "ctx");
zuptsdk_options_t *opts = NULL;
CHECK(zuptsdk_options_create(&opts), "opts");
zuptsdk_secure_buf_t *pw = NULL;
const uint8_t pwbuf[] = "Tr0ub4dor&3";
CHECK(zuptsdk_secure_buf_from_data(pwbuf, sizeof(pwbuf) - 1, &pw), "pw create");
uint8_t *arc = NULL; size_t arc_sz = 0;
int rc = zuptsdk_compress_buffer(ctx, opts, "secret.txt",
TEST_DATA, sizeof(TEST_DATA) - 1,
pw, NULL, &arc, &arc_sz);
if (rc != ZUPTSDK_OK) {
FAIL("compress");
zuptsdk_secure_buf_destroy(pw);
zuptsdk_options_destroy(opts);
zuptsdk_ctx_destroy(ctx);
return;
}
uint8_t *out = NULL; size_t out_sz = 0;
rc = zuptsdk_extract_buffer(ctx, arc, arc_sz, pw, NULL, &out, &out_sz);
zuptsdk_free(arc);
int ok = (rc == ZUPTSDK_OK) && byteexact(out, out_sz, TEST_DATA, sizeof(TEST_DATA) - 1);
zuptsdk_free(out);
zuptsdk_secure_buf_destroy(pw);
zuptsdk_options_destroy(opts);
zuptsdk_ctx_destroy(ctx);
if (!ok) { FAIL("byte mismatch or rc != OK"); return; }
PASS();
}
static void test_compress_buffer_wrong_password(void) {
TEST("extract_buffer rejects wrong password");
zuptsdk_ctx_t *ctx = NULL;
CHECK(zuptsdk_ctx_create(&ctx), "ctx");
zuptsdk_options_t *opts = NULL;
CHECK(zuptsdk_options_create(&opts), "opts");
zuptsdk_secure_buf_t *pw_good = NULL, *pw_bad = NULL;
zuptsdk_secure_buf_from_data((const uint8_t*)"correct", 7, &pw_good);
zuptsdk_secure_buf_from_data((const uint8_t*)"WRONG-pw", 8, &pw_bad);
uint8_t *arc = NULL; size_t arc_sz = 0;
int rc = zuptsdk_compress_buffer(ctx, opts, "x.txt",
TEST_DATA, sizeof(TEST_DATA) - 1,
pw_good, NULL, &arc, &arc_sz);
if (rc != ZUPTSDK_OK) { FAIL("compress"); goto cleanup; }
uint8_t *out = NULL; size_t out_sz = 0;
rc = zuptsdk_extract_buffer(ctx, arc, arc_sz, pw_bad, NULL, &out, &out_sz);
if (rc == ZUPTSDK_OK) {
FAIL("wrong password accepted");
zuptsdk_free(out);
zuptsdk_free(arc);
goto cleanup;
}
zuptsdk_free(arc);
PASS();
cleanup:
zuptsdk_secure_buf_destroy(pw_good);
zuptsdk_secure_buf_destroy(pw_bad);
zuptsdk_options_destroy(opts);
zuptsdk_ctx_destroy(ctx);
}
static void test_keypair_pq(void) {
TEST("keypair_generate + compress_pq + extract_pq");
zuptsdk_ctx_t *ctx = NULL;
CHECK(zuptsdk_ctx_create(&ctx), "ctx");
zuptsdk_keypair_t *kp = NULL;
int rc = zuptsdk_keypair_generate(ctx, &kp);
if (rc != ZUPTSDK_OK) { FAIL("keygen"); zuptsdk_ctx_destroy(ctx); return; }
/* Save and load to exercise that path too */
rc = zuptsdk_keypair_save_private(kp, "/tmp/_zsdk_priv.key");
if (rc != ZUPTSDK_OK) { FAIL("save priv"); goto err; }
rc = zuptsdk_keypair_save_public(kp, "/tmp/_zsdk_pub.key");
if (rc != ZUPTSDK_OK) { FAIL("save pub"); goto err; }
zuptsdk_pubkey_t *pub = NULL;
zuptsdk_privkey_t *priv = NULL;
rc = zuptsdk_pubkey_load("/tmp/_zsdk_pub.key", &pub);
if (rc != ZUPTSDK_OK) { FAIL("load pub"); goto err; }
rc = zuptsdk_privkey_load("/tmp/_zsdk_priv.key", &priv);
if (rc != ZUPTSDK_OK) { FAIL("load priv"); zuptsdk_pubkey_destroy(pub); goto err; }
zuptsdk_options_t *opts = NULL;
zuptsdk_options_create(&opts);
uint8_t *arc = NULL; size_t arc_sz = 0;
rc = zuptsdk_compress_buffer(ctx, opts, "pq.txt",
TEST_DATA, sizeof(TEST_DATA) - 1,
NULL, pub, &arc, &arc_sz);
if (rc != ZUPTSDK_OK) {
FAIL("compress_pq");
zuptsdk_pubkey_destroy(pub);
zuptsdk_privkey_destroy(priv);
zuptsdk_options_destroy(opts);
goto err;
}
uint8_t *out = NULL; size_t out_sz = 0;
rc = zuptsdk_extract_buffer(ctx, arc, arc_sz, NULL, priv, &out, &out_sz);
int ok = (rc == ZUPTSDK_OK) && byteexact(out, out_sz, TEST_DATA, sizeof(TEST_DATA) - 1);
zuptsdk_free(arc);
zuptsdk_free(out);
zuptsdk_pubkey_destroy(pub);
zuptsdk_privkey_destroy(priv);
zuptsdk_options_destroy(opts);
unlink("/tmp/_zsdk_priv.key");
unlink("/tmp/_zsdk_pub.key");
if (!ok) { FAIL("byte mismatch or rc != OK"); zuptsdk_keypair_destroy(kp); zuptsdk_ctx_destroy(ctx); return; }
zuptsdk_keypair_destroy(kp);
zuptsdk_ctx_destroy(ctx);
PASS();
return;
err:
zuptsdk_keypair_destroy(kp);
zuptsdk_ctx_destroy(ctx);
}
static void test_verify_and_info(void) {
TEST("verify and archive_info_read");
zuptsdk_ctx_t *ctx = NULL;
CHECK(zuptsdk_ctx_create(&ctx), "ctx");
zuptsdk_options_t *opts = NULL;
zuptsdk_options_create(&opts);
uint8_t *arc = NULL; size_t arc_sz = 0;
int rc = zuptsdk_compress_buffer(ctx, opts, "v.txt",
TEST_DATA, sizeof(TEST_DATA) - 1,
NULL, NULL, &arc, &arc_sz);
if (rc != ZUPTSDK_OK) { FAIL("compress"); goto cleanup; }
rc = zuptsdk_verify(ctx, arc, arc_sz, NULL, NULL);
if (rc != ZUPTSDK_OK) { FAIL("verify"); zuptsdk_free(arc); goto cleanup; }
zuptsdk_archive_info_t *info = NULL;
rc = zuptsdk_archive_info_read(ctx, arc, arc_sz, &info);
if (rc != ZUPTSDK_OK) { FAIL("info read"); zuptsdk_free(arc); goto cleanup; }
int major = zuptsdk_archive_info_format_major(info);
int enc = zuptsdk_archive_info_is_encrypted(info);
const char *uuid = zuptsdk_archive_info_uuid(info);
if (major < 1 || enc != 0 || !uuid || strlen(uuid) != 36) {
fprintf(stderr, "[major=%d enc=%d uuid=%s] ", major, enc, uuid ? uuid : "NULL");
FAIL("info fields wrong");
zuptsdk_archive_info_destroy(info);
zuptsdk_free(arc);
goto cleanup;
}
zuptsdk_archive_info_destroy(info);
zuptsdk_free(arc);
PASS();
cleanup:
zuptsdk_options_destroy(opts);
zuptsdk_ctx_destroy(ctx);
}
static void test_corrupted_archive(void) {
TEST("verify rejects corrupted archive");
zuptsdk_ctx_t *ctx = NULL;
CHECK(zuptsdk_ctx_create(&ctx), "ctx");
zuptsdk_options_t *opts = NULL;
zuptsdk_options_create(&opts);
/* Build a larger archive so flipping a byte hits compressed data,
* not a zero-padded header field. */
size_t big_sz = (sizeof(TEST_DATA) - 1) * 100;
uint8_t *big = (uint8_t *)malloc(big_sz);
if (!big) { FAIL("alloc"); goto cleanup; }
for (size_t i = 0; i < 100; i++)
memcpy(big + i * (sizeof(TEST_DATA) - 1), TEST_DATA, sizeof(TEST_DATA) - 1);
uint8_t *arc = NULL; size_t arc_sz = 0;
int rc = zuptsdk_compress_buffer(ctx, opts, "c.txt", big, big_sz,
NULL, NULL, &arc, &arc_sz);
free(big);
if (rc != ZUPTSDK_OK) { FAIL("compress"); goto cleanup; }
/* Flip a byte two-thirds through, in the real compressed data region */
if (arc_sz > 200) arc[(arc_sz * 2) / 3] ^= 0xFF;
rc = zuptsdk_verify(ctx, arc, arc_sz, NULL, NULL);
if (rc == ZUPTSDK_OK) {
FAIL("verify accepted corrupted archive");
zuptsdk_free(arc);
goto cleanup;
}
zuptsdk_free(arc);
PASS();
cleanup:
zuptsdk_options_destroy(opts);
zuptsdk_ctx_destroy(ctx);
}
int main(void) {
fprintf(stderr, "\n═══════════════════════════════════════════════════════════════\n");
fprintf(stderr, " libzuptsdk %s — roundtrip test suite\n", zuptsdk_version_string());
fprintf(stderr, "═══════════════════════════════════════════════════════════════\n\n");
test_version();
test_strerror();
test_context();
test_options();
test_secure_buf();
test_compress_buffer_plain();
test_compress_buffer_password();
test_compress_buffer_wrong_password();
test_keypair_pq();
test_verify_and_info();
test_corrupted_archive();
fprintf(stderr, "\n═══════════════════════════════════════════════════════════════\n");
fprintf(stderr, " Results: %d passed, %d failed\n", g_pass, g_fail);
fprintf(stderr, "═══════════════════════════════════════════════════════════════\n\n");
return g_fail == 0 ? 0 : 1;
}