// xxHash/tests/sanity_test_vectors_generator.c
// SPDX-License-Identifier: GPL-2.0-only
//
// So far, this program just generates sanity_test_vectors.h
//
// Building
// ========
//
// cc sanity_test_vectors_generator.c && ./a.out
// less sanity_test_vectors.h
//
#define XXH_STATIC_LINKING_ONLY
#define XXH_IMPLEMENTATION   /* access definitions */
#include "../cli/xsum_arch.h"
#include "../cli/xsum_os_specific.h"
#include "../xxhash.h"

#include <assert.h> /* assert */

/* use #define to make them constant, required for initialization */
#define PRIME32 2654435761U
#define PRIME64 11400714785074694797ULL

#define SANITY_BUFFER_SIZE (4096 + 64 + 1)


/* TODO : Share these test vector definitions with sanity_check.c and xsum_sanity_check.c */
/*
 * Test data vectors
 */
typedef struct {
    XSUM_U32 len;
    XSUM_U32 seed;
    XSUM_U32 Nresult;
} XSUM_testdata32_t;

typedef struct {
    XSUM_U32 len;
    XSUM_U64 seed;
    XSUM_U64 Nresult;
} XSUM_testdata64_t;

typedef struct {
    XSUM_U32 len;
    XSUM_U64 seed;
    XXH128_hash_t Nresult;
} XSUM_testdata128_t;

#define SECRET_SAMPLE_NBBYTES 5
typedef struct {
    XSUM_U32 seedLen;
    XSUM_U32 secretLen;
    XSUM_U8 byte[SECRET_SAMPLE_NBBYTES];
} XSUM_testdata_sample_t;

#ifndef   SECRET_SIZE_MAX
#  define SECRET_SIZE_MAX 9867
#endif


/* TODO : Share these generators with sanity_check.c and xsum_sanity_check.c */
/* Test vector generators */
static XSUM_testdata32_t tvgen_XXH32(const XSUM_U8* buf, size_t len, XSUM_U32 seed) {
    XSUM_testdata32_t v;
    v.len     = len;
    v.seed    = seed;
    v.Nresult = XXH32(buf, len, seed);
    return v;
}

static XSUM_testdata64_t tvgen_XXH64(const XSUM_U8* buf, size_t len, XSUM_U64 seed) {
    XSUM_testdata64_t v;
    v.len     = len;
    v.seed    = seed;
    v.Nresult = XXH64(buf, len, seed);
    return v;
}

static XSUM_testdata64_t tvgen_XXH3_64bits_withSeed(const XSUM_U8* buf, size_t len, XSUM_U64 seed) {
    XSUM_testdata64_t v;
    v.len     = len;
    v.seed    = seed;
    v.Nresult = XXH3_64bits_withSeed(buf, len, seed);
    return v;
}

static XSUM_testdata64_t tvgen_XXH3_64bits_withSecret(const XSUM_U8* buf, size_t len, const void* secret, size_t secretSize) {
    XSUM_testdata64_t v;
    v.len     = len;
    v.seed    = 0;
    v.Nresult = XXH3_64bits_withSecret(buf, len, secret, secretSize);
    return v;
}

static XSUM_testdata128_t tvgen_XXH3_128bits_withSeed(const XSUM_U8* buf, size_t len, XSUM_U64 seed) {
    XSUM_testdata128_t v;
    v.len     = len;
    v.seed    = seed;
    v.Nresult = XXH3_128bits_withSeed(buf, len, seed);
    return v;
}

static XSUM_testdata128_t tvgen_XXH3_128bits_withSecret(const XSUM_U8* buf, size_t len, const void* secret, size_t secretSize) {
    XSUM_testdata128_t v;
    v.len     = len;
    v.seed    = 0;
    v.Nresult = XXH3_128bits_withSecret(buf, len, secret, secretSize);
    return v;
}

static XSUM_testdata_sample_t tvgen_XXH3_generateSecret(
    void* secretBuffer,
    size_t secretSize,
    const void* customSeed,
    size_t customSeedSize
) {
    XXH3_generateSecret(secretBuffer, secretSize, customSeed, customSeedSize);

    XSUM_testdata_sample_t v;
    v.seedLen   = customSeedSize;
    v.secretLen = secretSize;

    /* TODO : Share this array with sanity_check.c and xsum_sanity_check.c */
    /* position of sampled bytes */
    static const int sampleIndex[SECRET_SAMPLE_NBBYTES] = { 0, 62, 131, 191, 241 };

    for(int i = 0; i < SECRET_SAMPLE_NBBYTES; ++i) {
        const XSUM_U8* const secretBufferAsU8 = (const XSUM_U8*) secretBuffer;
        v.byte[i] = secretBufferAsU8[sampleIndex[i]];
    }
    return v;
}


/* Test vector serializers */
static void fprintf_XSUM_testdata32_t(FILE* fp, XSUM_testdata32_t const v) {
    fprintf(fp, "{ %4d, 0x%08XU, 0x%08XU },", v.len, v.seed, v.Nresult);
}

static void fprintf_XSUM_testdata64_t(FILE* fp, XSUM_testdata64_t const v) {
    fprintf(fp, "{ %4d, 0x%016zXULL, 0x%016zXULL },", v.len, v.seed, v.Nresult);
}

static void fprintf_XSUM_testdata128_t(FILE* fp, XSUM_testdata128_t const v) {
    fprintf(fp, "{ %4d, 0x%016zXULL, { 0x%016zXULL, 0x%016zXULL } },",
            v.len, v.seed, v.Nresult.low64, v.Nresult.high64);
}

static void fprintf_XSUM_testdata_sample_t(FILE* fp, XSUM_testdata_sample_t const v) {
    fprintf(fp,"{ %4d, %4d, { 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X } },",
            v.seedLen, v.secretLen, v.byte[0], v.byte[1], v.byte[2], v.byte[3], v.byte[4]);
}


/* TODO : Share this function with sanity_check.c and xsum_sanity_check.c */
/*
 * Fills a test buffer with pseudorandom data.
 *
 * This is used in the sanity check - its values must not be changed.
 */
static void fillTestBuffer(XSUM_U8* buffer, size_t bufferLenInBytes)
{
    XSUM_U64 byteGen = PRIME32;
    size_t i;

    assert(buffer != NULL);

    for (i = 0; i < bufferLenInBytes; ++i) {
        buffer[i] = (XSUM_U8)(byteGen>>56);
        byteGen *= PRIME64;
    }
}


/* TODO : Share this function with sanity_check.c and xsum_sanity_check.c */
/*
 * Create (malloc) and fill buffer with pseudorandom data for sanity check.
 *
 * Use releaseSanityBuffer() to delete the buffer.
 */
static XSUM_U8* createSanityBuffer(size_t bufferLenInBytes)
{
    XSUM_U8* const buffer = (XSUM_U8*) malloc(bufferLenInBytes);
    assert(buffer != NULL);
    fillTestBuffer(buffer, bufferLenInBytes);
    return buffer;
}


/* TODO : Share this function with sanity_check.c and xsum_sanity_check.c */
/*
 * Delete (free) the buffer which has been genereated by createSanityBuffer()
 */
static void releaseSanityBuffer(XSUM_U8* buffer)
{
    assert(buffer != NULL);
    free(buffer);
}


/* Generate test vectors for XXH32() */
static void generate_sanity_test_vectors_xxh32(FILE* fp, size_t maxLen) {
    const char* const arrayTypeName = "XSUM_testdata32_t";
    const char* const arrayName     = "XSUM_XXH32_testdata";
    fprintf(fp, "static const %s %s[] = {\n", arrayTypeName, arrayName);

    XSUM_U8* const sanityBuffer = createSanityBuffer(maxLen);

    size_t index = 0;
    for(size_t len = 0; len < maxLen; ++len) {
        static const size_t seeds[] = { 0, PRIME32 };
        for(size_t iSeed = 0; iSeed < sizeof(seeds)/sizeof(seeds[0]); ++iSeed) {
            size_t const seed = seeds[iSeed];
            XSUM_testdata32_t const v = tvgen_XXH32(sanityBuffer, len, seed);

            fprintf(fp, "    ");
            fprintf_XSUM_testdata32_t(fp, v);
            fprintf(fp, " /* %s[%zd] */\n", arrayName, index++);
        }
    }

    releaseSanityBuffer(sanityBuffer);
    fprintf(fp, "};\n");
}


/* Generate test vectors for XXH64() */
static void generate_sanity_test_vectors_xxh64(FILE* fp, size_t maxLen) {
    const char* const arrayTypeName = "XSUM_testdata64_t";
    const char* const arrayName     = "XSUM_XXH64_testdata";
    fprintf(fp, "static const %s %s[] = {\n", arrayTypeName, arrayName);

    XSUM_U8* const sanityBuffer = createSanityBuffer(maxLen);

    size_t index = 0;
    for(size_t len = 0; len < maxLen; ++len) {
        static const size_t seeds[] = { 0, PRIME32 };
        for(size_t iSeed = 0; iSeed < sizeof(seeds)/sizeof(seeds[0]); ++iSeed) {
            size_t const seed = seeds[iSeed];
            XSUM_testdata64_t const v = tvgen_XXH64(sanityBuffer, len, seed);

            fprintf(fp, "    ");
            fprintf_XSUM_testdata64_t(fp, v);
            fprintf(fp, " /* %s[%zd] */\n", arrayName, index++);
        }
    }

    releaseSanityBuffer(sanityBuffer);
    fprintf(fp, "};\n");
}


/* Generate test vectors for XXH3_64bits_withSeed() */
static void generate_sanity_test_vectors_xxh3(FILE* fp, size_t maxLen) {
    const char* const arrayTypeName = "XSUM_testdata64_t";
    const char* const arrayName     = "XSUM_XXH3_testdata";
    fprintf(fp, "static const %s %s[] = {\n", arrayTypeName, arrayName);

    XSUM_U8* const sanityBuffer = createSanityBuffer(maxLen);

    size_t index = 0;
    for(size_t len = 0; len < maxLen; ++len) {
        static const size_t seeds[] = { 0, PRIME64 };
        for(size_t iSeed = 0; iSeed < sizeof(seeds)/sizeof(seeds[0]); ++iSeed) {
            size_t const seed = seeds[iSeed];
            XSUM_testdata64_t const v = tvgen_XXH3_64bits_withSeed(sanityBuffer, len, seed);

            fprintf(fp, "    ");
            fprintf_XSUM_testdata64_t(fp, v);
            fprintf(fp, " /* %s[%zd] */\n", arrayName, index++);
        }
    }

    releaseSanityBuffer(sanityBuffer);
    fprintf(fp, "};\n");
}


/* Generate test vectors for XXH3_64bits_withSecret() */
static void generate_sanity_test_vectors_xxh3_withSecret(FILE* fp, size_t maxLen) {
    const char* const arrayTypeName = "XSUM_testdata64_t";
    const char* const arrayName     = "XSUM_XXH3_withSecret_testdata";
    fprintf(fp, "static const %s %s[] = {\n", arrayTypeName, arrayName);

    XSUM_U8* const sanityBuffer = createSanityBuffer(maxLen);

    const void* const secret = sanityBuffer + 7;
    size_t const secretSize = XXH3_SECRET_SIZE_MIN + 11;
    assert(maxLen >= 7 + secretSize);

    size_t index = 0;
    for(size_t len = 0; len < maxLen; ++len) {
        XSUM_testdata64_t const v = tvgen_XXH3_64bits_withSecret(sanityBuffer, len, secret, secretSize);

        fprintf(fp, "    ");
        fprintf_XSUM_testdata64_t(fp, v);
        fprintf(fp, " /* %s[%zd] */\n", arrayName, index++);
    }

    releaseSanityBuffer(sanityBuffer);
    fprintf(fp, "};\n");
}


/* Generate test vectors for XXH3_128bits_withSeed() */
static void generate_sanity_test_vectors_xxh128(FILE* fp, size_t maxLen) {
    const char* const arrayTypeName = "XSUM_testdata128_t";
    const char* const arrayName     = "XSUM_XXH128_testdata";
    fprintf(fp, "static const %s %s[] = {\n", arrayTypeName, arrayName);

    XSUM_U8* const sanityBuffer = createSanityBuffer(maxLen);

    size_t index = 0;
    for(size_t len = 0; len < maxLen; ++len) {
        static const size_t seeds[] = { 0, PRIME32, PRIME64 };
        for(size_t iSeed = 0; iSeed < sizeof(seeds)/sizeof(seeds[0]); ++iSeed) {
            XSUM_U64 const seed = seeds[iSeed];
            XSUM_testdata128_t const v = tvgen_XXH3_128bits_withSeed(sanityBuffer, len, seed);

            fprintf(fp, "    ");
            fprintf_XSUM_testdata128_t(fp, v);
            fprintf(fp, " /* %s[%zd] */\n", arrayName, index++);
        }
    }
    fprintf(fp, "};\n");
    releaseSanityBuffer(sanityBuffer);
}


/* Generate test vectors for XXH3_128bits_withSecret() */
static void generate_sanity_test_vectors_xxh128_withSecret(FILE* fp, size_t maxLen) {
    const char* const arrayTypeName = "XSUM_testdata128_t";
    const char* const arrayName     = "XSUM_XXH128_withSecret_testdata";
    fprintf(fp, "static const %s %s[] = {\n", arrayTypeName, arrayName);

    XSUM_U8* const sanityBuffer = createSanityBuffer(maxLen);

    const void* const secret = sanityBuffer + 7;
    size_t const secretSize = XXH3_SECRET_SIZE_MIN + 11;
    assert(maxLen >= 7 + secretSize);

    size_t index = 0;
    for(size_t len = 0; len < maxLen; ++len) {
        XSUM_testdata128_t const v = tvgen_XXH3_128bits_withSecret(sanityBuffer, len, secret, secretSize);

        fprintf(fp, "    ");
        fprintf_XSUM_testdata128_t(fp, v);
        fprintf(fp, " /* %s[%zd] */\n", arrayName, index++);
    }

    fprintf(fp, "};\n");
    releaseSanityBuffer(sanityBuffer);
}


/* Generate test vectors for XXH3_generateSecret() */
static void generate_sanity_test_vectors_xxh3_generateSecret(FILE* fp, size_t maxLen) {
    const char* const arrayTypeName = "XSUM_testdata_sample_t";
    const char* const arrayName     = "XSUM_XXH3_generateSecret_testdata";
    fprintf(fp, "static const %s %s[] = {\n", arrayTypeName, arrayName);

    XSUM_U8* const sanityBuffer = createSanityBuffer(maxLen);
    const void* const customSeed = sanityBuffer;
    static const size_t seedLens[] = {
        0,
        1,
        XXH3_SECRET_SIZE_MIN - 1,
        XXH3_SECRET_DEFAULT_SIZE + 500
    };
    static const size_t secretLens[] = {
        192,
        240,
        277,
        SECRET_SIZE_MAX
    };

    size_t index = 0;
    for(size_t iSeedLen = 0; iSeedLen < sizeof(seedLens)/sizeof(seedLens[0]); ++iSeedLen) {
        for(size_t iSecretLen = 0; iSecretLen < sizeof(secretLens)/sizeof(secretLens[0]); ++iSecretLen) {
            size_t const seedLen = seedLens[iSeedLen];
            size_t const secretLen = secretLens[iSecretLen];
            XSUM_U8 secretBuffer[SECRET_SIZE_MAX] = {0};

            assert(seedLen <= maxLen);
            assert(secretLen <= SECRET_SIZE_MAX);

            XSUM_testdata_sample_t const v = tvgen_XXH3_generateSecret(
                secretBuffer,
                secretLen,
                customSeed,
                seedLen
            );

            fprintf(fp, "    ");
            fprintf_XSUM_testdata_sample_t(fp, v);
            fprintf(fp, " /* %s[%zd] */\n", arrayName, index++);
        }
    }
    fprintf(fp, "};\n");

    releaseSanityBuffer(sanityBuffer);
}


/* Generate test vectors */
void generate_sanity_test_vectors(size_t maxLen) {
    const char* filename = "sanity_test_vectors.h";
    fprintf(stderr, "Generating %s\n", filename);
    FILE* fp = fopen(filename, "w");
    fprintf(fp,
        "typedef struct {\n"
        "    XSUM_U32 len;\n"
        "    XSUM_U32 seed;\n"
        "    XSUM_U32 Nresult;\n"
        "} XSUM_testdata32_t;\n"
        "\n"
        "typedef struct {\n"
        "    XSUM_U32 len;\n"
        "    XSUM_U64 seed;\n"
        "    XSUM_U64 Nresult;\n"
        "} XSUM_testdata64_t;\n"
        "\n"
        "typedef struct {\n"
        "    XSUM_U32 len;\n"
        "    XSUM_U64 seed;\n"
        "    XXH128_hash_t Nresult;\n"
        "} XSUM_testdata128_t;\n"
        "\n"
        "#ifndef SECRET_SAMPLE_NBBYTES\n"
        "#define SECRET_SAMPLE_NBBYTES 5\n"
        "#endif\n"
        "\n"
        "typedef struct {\n"
        "    XSUM_U32 seedLen;\n"
        "    XSUM_U32 secretLen;\n"
        "    XSUM_U8 byte[SECRET_SAMPLE_NBBYTES];\n"
        "} XSUM_testdata_sample_t;\n"
        "\n"
        "#ifndef SECRET_SIZE_MAX\n"
        "#define SECRET_SIZE_MAX 9867\n"
        "#endif\n"
        "\n"
    );

    generate_sanity_test_vectors_xxh32(fp, maxLen);
    generate_sanity_test_vectors_xxh64(fp, maxLen);
    generate_sanity_test_vectors_xxh3(fp, maxLen);
    generate_sanity_test_vectors_xxh3_withSecret(fp, maxLen);
    generate_sanity_test_vectors_xxh128(fp, maxLen);
    generate_sanity_test_vectors_xxh128_withSecret(fp, maxLen);
    generate_sanity_test_vectors_xxh3_generateSecret(fp, maxLen);
    fclose(fp);
}


/**/
int main(int argc, const char* argv[])
{
    (void) argc;
    (void) argv;
    const size_t sanityBufferSizeInBytes = SANITY_BUFFER_SIZE;
    generate_sanity_test_vectors(sanityBufferSizeInBytes);
    return EXIT_SUCCESS;
}
