#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright (c) (2020) Apple Inc. All rights reserved.
#
# corecrypto is licensed under Apple Inc.’s Internal Use License Agreement (which
# is contained in the License.txt file distributed with corecrypto) and only to
# people who accept that license. IMPORTANT:  Any license rights granted to you by
# Apple Inc. (if any) are limited to internal use within your organization only on
# devices and computers you own or control, for the sole purpose of verifying the
# security characteristics and correct functioning of the Apple Software.  You may
# not, directly or indirectly, redistribute the Apple Software or any portions thereof.

import os
import json
import binascii
import argparse

class Compiler(object):
    def __init__(self):
        self.syms = []
        self.decls = []
        self.symid = 0
        self.prefix = binascii.hexlify(os.urandom(6)).decode("utf-8")
        self.c_struct_data = ""
        self.test_vector_structs = []
        self.STRUCT = None

    def gen_sym(self, name):
        self.symid += 1
        return "cctest_{}_{}_{}".format(self.prefix, name, self.symid)

    def convert_hex_uint8t_list(self, name, data):
        _ = binascii.unhexlify(data) # Make sure it's hex
        data = ["0x{}".format(data[x:x+2]) for x in range(0, len(data), 2)]
        sym = self.gen_sym(name)
        self.c_struct_data += 'static const uint8_t {}[] = {{ {} }};\n'.format(sym, ', '.join(data))
        return (sym, len(data))

    def convert_struct(self, struct_name, name, data):
        sym = self.gen_sym(name)

        fields = []
        for k,v in data.items():
            fields.append('.{} = {}'.format(k, v))

        self.c_struct_data += 'static const struct {} {} = {{ {} }};\n'.format(
            self.STRUCT,
            sym,
            ', '.join(fields)
        )
        return sym


    def add_test_vector(self, name, data):
        assert self.STRUCT is not None
        
        sym = self.convert_struct(self.STRUCT, name, data)
        self.test_vector_structs.append("&{}".format(sym))
        return sym

    def finish_vectors(self, name):
        assert self.STRUCT is not None

        self.c_struct_data += 'static const struct {} *{}[] = {{ {} }};\n\n'.format(
            self.STRUCT,
            name,
            ', '.join(self.test_vector_structs)
        )
        return self.c_struct_data
    
class SSWU(Compiler):
    '''
    struct cch2c_sswu_test_vector {
        unsigned tcId;
        unsigned curve;
        unsigned hkdf;
        const uint8_t *alpha;
        size_t alpha_len;
        const uint8_t *DST;
        size_t DST_len;
        const uint8_t *NUx;
        size_t NUx_len;
        const uint8_t *NUy;
        size_t NUy_len;
        const uint8_t *ROx;
        size_t ROx_len;
        const uint8_t *ROy;
        size_t ROy_len;
        const uint8_t u_nu;
        size_t u_nu_len;
        const uint8_t u_ro0;
        size_t u_ro0_len;
        const uint8_t u_ro1;
        size_t u_ro1_len;
    };
    '''

    def __init__(self):
        super(SSWU, self).__init__()
        self.STRUCT = "cch2c_sswu_test_vector"
        self.UINT8T_BLOBS = ["alpha", "DST", "NUx", "NUy", "ROx", "ROy", "u_nu", "u_ro0", "u_ro1", "Q0x", "Q0y", "Q1x", "Q1y"]

    def process(self, json_data):
        for test_group in json_data["testGroups"]:
            for test in test_group["tests"]:

                test_vector = {}

                for blob_name in self.UINT8T_BLOBS:
                    blob_value = test.get(blob_name, None)
                    if blob_value is not None:
                        sym, data_len = self.convert_hex_uint8t_list(blob_name, blob_value)
                        test_vector[blob_name] = sym
                        test_vector["{}_len".format(blob_name)] = data_len

                test_vector["tcId"] = test.get("tcId", -1)
                test_vector["curve"] = test_group.get("curve", -1)
                test_vector["hkdf"] = test_group.get("hkdf", -1)

                self.add_test_vector("test_vector", test_vector)

TEST_VECTORS = {
    "h2c_sswu" : {
        "compiler": SSWU,
        "files": ["h2c/sswu.json"],
        "output_dir": "cch2c/test_vectors",
        "test_vectors_name": "h2c_sswu_vectors"
    }
}

def convert(srcroot):
    for test_name, test_struct in TEST_VECTORS.items():
        compiler = test_struct["compiler"]()
        input_files = list(map(lambda file: os.path.join(srcroot, "corecrypto_test/test_vectors", file), test_struct["files"]))
        output_file = os.path.join(srcroot, test_struct["output_dir"], '{}.kat'.format(test_name))
        print(output_file)

        with open(output_file, "w") as fout:
            for input_file in input_files:
                with open(input_file) as fin:
                    data = fin.read()
                    json_data = json.loads(data)
                    compiler.process(json_data)
            fout.write(compiler.finish_vectors(test_struct["test_vectors_name"]))

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description = "Convert Wycheproof test vectors into C structs")
    parser.add_argument("srcroot", help = "Repository srcroot")
    args = parser.parse_args()
    convert(args.srcroot)
