// SPDX-License-Identifier: BSD-2-Clause
/*
 * Copyright (c) 2019, Linaro Limited
 */

#include <ctype.h>
#include <endian.h>
#ifdef OPENSSL_FOUND
#include <openssl/evp.h>
#endif
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <tee_api_types.h>
#include <tee_client_api.h>
#include "xtest_uuid_helpers.h"

static int hex(char c)
{
	char lc = tolower(c);

	if (isdigit(lc))
		return lc - '0';
	if (isxdigit(lc))
		return lc - 'a' + 10;
	return -1;
}

static uint32_t parse_hex(const char *s, size_t nchars, uint32_t *res)
{
	uint32_t v = 0;
	size_t n = 0;
	int c = 0;

	for (n = 0; n < nchars; n++) {
		c = hex(s[n]);
		if (c == -1) {
			*res = TEE_ERROR_BAD_FORMAT;
			goto out;
		}
		v = (v << 4) + c;
	}
	*res = TEE_SUCCESS;
out:
	return v;
}

TEEC_Result xtest_uuid_from_str(TEEC_UUID *uuid, const char *s)
{
	TEEC_Result res = TEEC_SUCCESS;
	TEEC_UUID u = { };
	const char *p = s;
	size_t i = 0;

	if (!p || strnlen(p, 37) != 36)
		return TEEC_ERROR_BAD_FORMAT;
	if (p[8] != '-' || p[13] != '-' || p[18] != '-' || p[23] != '-')
		return TEEC_ERROR_BAD_FORMAT;

	u.timeLow = parse_hex(p, 8, &res);
	if (res)
		goto out;
	p += 9;
	u.timeMid = parse_hex(p, 4, &res);
	if (res)
		goto out;
	p += 5;
	u.timeHiAndVersion = parse_hex(p, 4, &res);
	if (res)
		goto out;
	p += 5;
	for (i = 0; i < 8; i++) {
		u.clockSeqAndNode[i] = parse_hex(p, 2, &res);
		if (res)
			goto out;
		if (i == 1)
			p += 3;
		else
			p += 2;
	}
	*uuid = u;
out:
	return res;
}

#define UUID_STR_LEN	37

char *xtest_uuid_to_allocated_str(const TEEC_UUID *uuid)
{
	char *s = NULL;

	s = malloc(UUID_STR_LEN);
	if (!s)
		return NULL;

	sprintf(s, "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", uuid->timeLow,
		uuid->timeMid, uuid->timeHiAndVersion, uuid->clockSeqAndNode[0],
		uuid->clockSeqAndNode[1], uuid->clockSeqAndNode[2],
		uuid->clockSeqAndNode[3], uuid->clockSeqAndNode[4],
		uuid->clockSeqAndNode[5], uuid->clockSeqAndNode[6],
		uuid->clockSeqAndNode[7]);

	return s;
}

#ifdef OPENSSL_FOUND
TEEC_Result xtest_uuid_v5(TEEC_UUID *uuid, const TEEC_UUID *ns,
			  const void *name, size_t size)
{
	TEEC_Result res = TEEC_SUCCESS;
	EVP_MD_CTX *mdctx = NULL;
	const EVP_MD *md = NULL;
	unsigned char hash[EVP_MAX_MD_SIZE] = { };
	unsigned int md_len = 0;
	unsigned char nsbe[16] = { };
	uint32_t be32 = 0;
	uint16_t be16 = 0;
	int ret = 0;

	/* Convert from host to big endian */
	be32 = htobe32(ns->timeLow);
	memcpy(&nsbe[0], &be32, sizeof(be32));
	be16 = htobe16(ns->timeMid);
	memcpy(&nsbe[4], &be16, sizeof(be16));
	be16 = htobe16(ns->timeHiAndVersion);
	memcpy(&nsbe[6], &be16, sizeof(be16));
	memcpy(&nsbe[8], &ns->clockSeqAndNode, sizeof(ns->clockSeqAndNode));

	mdctx = EVP_MD_CTX_create();
	if (!mdctx)
		return TEEC_ERROR_OUT_OF_MEMORY;
	md = EVP_sha1();
	if (!md) {
		res = TEEC_ERROR_NOT_SUPPORTED;
		goto out;
	}
	ret = EVP_DigestInit_ex(mdctx, md, NULL);
	if (!ret) {
		res = TEEC_ERROR_GENERIC;
		goto out;
	}
	ret = EVP_DigestUpdate(mdctx, nsbe, sizeof(nsbe));
	if (!ret) {
		res = TEEC_ERROR_GENERIC;
		goto out;
	}
	ret = EVP_DigestUpdate(mdctx, name, size);
	if (!ret) {
		res = TEEC_ERROR_GENERIC;
		goto out;
	}
	ret = EVP_DigestFinal_ex(mdctx, hash, &md_len);
	if (!ret) {
		res = TEEC_ERROR_GENERIC;
		goto out;
	}

	/* Mark it as UUIDv5 */
	hash[6] = (hash[6] & 0x0F) | 0x50;
	hash[8] = (hash[8] & 0x3F) | 0x80;

	/* Convert from big endian to host */
	memcpy(&be32, &hash[0], sizeof(uint32_t));
	uuid->timeLow = be32toh(be32);
	memcpy(&be16, &hash[4], sizeof(uint16_t));
	uuid->timeMid = be16toh(be16);
	memcpy(&be16, &hash[6], sizeof(uint16_t));
	uuid->timeHiAndVersion = be16toh(be16);
	memcpy(uuid->clockSeqAndNode, &hash[8], sizeof(uuid->clockSeqAndNode));
out:
	EVP_MD_CTX_destroy(mdctx);
	return res;
}
#endif /*OPENSSL_FOUND*/
