Files
zeekay f77da86cda Initial luxfi/license — Ed25519-signed offline license tokens for commercial Lux features.
- Stdlib-only deterministic binary token format (no JWT / CBOR dep).
- Go API: VerifyToken, LoadFromEnv, HasFeature, HasFeatureOrDie.
- Stable C ABI + C++17 RAII wrapper for non-Go commercial binaries (luxcpp/cevm).
- cmd/lux-license: keygen / issue / inspect / verify.
- Embedded development key; warns once at runtime until production key swap.
- 22 Go tests + 1 cgo round-trip test, all passing.
2026-05-15 17:08:32 -07:00

101 lines
3.6 KiB
C

/*
* Copyright (C) 2020-2026, Lux Industries Inc. All rights reserved.
* See the file LICENSE for licensing terms.
*
* test_capi.c — round-trip test for the C ABI of luxfi/license.
*
* Build (assumes liblux_license.a + lux_license.h already produced by
* `go build -buildmode=c-archive`):
*
* cc -I. test_capi.c liblux_license.a -o test_capi -lpthread \
* $(pkg-config --libs libssl 2>/dev/null)
*
* Run with $LUX_LICENSE set to a base64 token signed by the dev key.
*/
#include "lux_license.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void must(int cond, const char* msg) {
if (!cond) {
fprintf(stderr, "FAIL: %s\n", msg);
const char* e = lux_license_last_error();
if (e != NULL && e[0] != '\0') {
fprintf(stderr, " last error: %s\n", e);
}
exit(1);
}
}
int main(void) {
const char* env_token = getenv("LUX_LICENSE");
if (env_token == NULL) {
fprintf(stderr, "FAIL: $LUX_LICENSE must be set for this test\n");
return 2;
}
/* 1. verify directly via lux_license_verify_base64 */
LuxLicenseClaims* c = lux_license_verify_base64(env_token);
must(c != NULL, "verify_base64 returned NULL");
const char* cust = lux_license_customer_id(c);
must(cust != NULL && strcmp(cust, "Acme Inc") == 0, "customer_id mismatch");
const char* lid = lux_license_license_id(c);
must(lid != NULL && strncmp(lid, "lic_", 4) == 0, "license_id missing prefix");
must(lux_license_has_feature(c, "gpu") == 1, "expected gpu feature");
must(lux_license_has_feature(c, "dex") == 1, "expected dex feature");
must(lux_license_has_feature(c, "fpga") == 1, "expected fpga feature");
must(lux_license_has_feature(c, "missing") == 0, "unexpected feature");
int n = lux_license_scope_count(c);
must(n == 3, "expected 3 scopes");
/* Scopes are sorted: dex, fpga, gpu */
const char* s0 = lux_license_scope(c, 0);
const char* s1 = lux_license_scope(c, 1);
const char* s2 = lux_license_scope(c, 2);
must(s0 != NULL && strcmp(s0, "dex") == 0, "scope[0] != dex");
must(s1 != NULL && strcmp(s1, "fpga") == 0, "scope[1] != fpga");
must(s2 != NULL && strcmp(s2, "gpu") == 0, "scope[2] != gpu");
must(lux_license_scope(c, 99) == NULL, "OOB scope should be NULL");
int64_t iat = lux_license_issued_at_unix(c);
int64_t exp = lux_license_expiry_unix(c);
must(iat > 0, "iat == 0");
must(exp > iat, "exp <= iat");
lux_license_free(c);
/* 2. tampered token rejected */
char* tampered = strdup(env_token);
/* flip a base64 char in the middle of the payload */
size_t mid = strlen(tampered) / 2;
tampered[mid] = (tampered[mid] == 'A') ? 'B' : 'A';
LuxLicenseClaims* bad = lux_license_verify_base64(tampered);
must(bad == NULL, "tampered token should fail to verify");
const char* berr = lux_license_last_error();
must(berr != NULL && berr[0] != '\0', "expected last_error to be set");
free(tampered);
/* 3. load_from_env path */
LuxLicenseClaims* env_c = lux_license_load_from_env();
must(env_c != NULL, "load_from_env returned NULL");
must(lux_license_has_feature(env_c, "gpu") == 1, "env gpu feature");
lux_license_free(env_c);
/* 4. NULL handling */
must(lux_license_verify_base64(NULL) == NULL, "NULL token should fail");
must(lux_license_has_feature(NULL, "gpu") == 0, "NULL handle has_feature");
must(lux_license_customer_id(NULL) == NULL, "NULL handle customer_id");
lux_license_free(NULL); /* must not crash */
fprintf(stdout, "PASS: lux_license C ABI round-trip\n");
return 0;
}