// Copyright(c) 2017-2020, Intel Corporation
//
// Redistribution  and  use  in source  and  binary  forms,  with  or  without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of  source code  must retain the  above copyright notice,
//   this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
// * Neither the name  of Intel Corporation  nor the names of its contributors
//   may be used to  endorse or promote  products derived  from this  software
//   without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,  BUT NOT LIMITED TO,  THE
// IMPLIED WARRANTIES OF  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT  SHALL THE COPYRIGHT OWNER  OR CONTRIBUTORS BE
// LIABLE  FOR  ANY  DIRECT,  INDIRECT,  INCIDENTAL,  SPECIAL,  EXEMPLARY,  OR
// CONSEQUENTIAL  DAMAGES  (INCLUDING,  BUT  NOT LIMITED  TO,  PROCUREMENT  OF
// SUBSTITUTE GOODS OR SERVICES;  LOSS OF USE,  DATA, OR PROFITS;  OR BUSINESS
// INTERRUPTION)  HOWEVER CAUSED  AND ON ANY THEORY  OF LIABILITY,  WHETHER IN
// CONTRACT,  STRICT LIABILITY,  OR TORT  (INCLUDING NEGLIGENCE  OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,  EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif // HAVE_CONFIG_H

#include <string.h>
#include <uuid/uuid.h>
#include <json-c/json.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "opae/utils.h"

#include "common_int.h"
#include "bitstream_int.h"

#define METADATA_GUID "58656F6E-4650-4741-B747-425376303031"
#define METADATA_GUID_LEN 16
#define METADATA_MAX_LEN 8192
#define FPGA_GBS_6_3_0_MAGIC	0x1d1f8680 // dec: 488605312
#define PR_INTERFACE_ID 	"pr/interface_id"
#define INTFC_ID_LOW_LEN	16
#define INTFC_ID_HIGH_LEN	16
#define BUFFER_SIZE		32

// GBS json metadata
// GBS version
#define GBS_VERSION                                 "version"

// AFU image
#define GBS_AFU_IMAGE                               "afu-image"
#define GBS_MAGIC_NUM                               "magic-no"
#define BBS_INTERFACE_ID                            "interface-uuid"
#define GBS_CLOCK_FREQUENCY_HIGH                    "clock-frequency-high"
#define GBS_CLOCK_FREQUENCY_LOW                     "clock-frequency-low"
#define GBS_AFU_POWER                               "power"

// AFU Clusters
#define GBS_ACCELERATOR_CLUSTERS                    "accelerator-clusters"
#define GBS_AFU_NAME                                "name"
#define GBS_ACCELERATOR_TYPE_UUID                   "accelerator-type-uuid"
#define GBS_ACCELERATOR_TOTAL_CONTEXTS              "total-contexts"


fpga_result string_to_guid(const char *guid, fpga_guid *result)
{
	if (uuid_parse(guid, *result) < 0) {
		OPAE_MSG("Error parsing GUID %s\n", guid);
		return FPGA_INVALID_PARAM;
	}

	return FPGA_OK;
}

STATIC json_bool get_json_object(json_object **object, json_object **parent,
				char *field_name)
{
	return json_object_object_get_ex(*parent, field_name, &(*object));
}

STATIC uint64_t read_int_from_bitstream(const uint8_t *bitstream, uint8_t size)
{
	uint64_t ret = 0;
	switch (size) {

	case sizeof(uint8_t):
		ret = *((uint8_t *) bitstream);
		break;
	case sizeof(uint16_t):
		ret = *((uint16_t *) bitstream);
		break;
	case sizeof(uint32_t):
		ret = *((uint32_t *) bitstream);
		break;
	case sizeof(uint64_t):
		ret = *((uint64_t *) bitstream);
		break;
	default:
		OPAE_ERR("Unknown integer size");
	}

	return ret;
}

STATIC int64_t int64_be_to_le(int64_t val)
{
	val = ((val << 8) & 0xFF00FF00FF00FF00ULL) |
					((val >> 8) & 0x00FF00FF00FF00FFULL);
	val = ((val << 16) & 0xFFFF0000FFFF0000ULL) |
					((val >> 16) & 0x0000FFFF0000FFFFULL);
	return (val << 32) | ((val >> 32) & 0xFFFFFFFFULL);
}

fpga_result get_interface_id(fpga_handle handle, uint64_t *id_l, uint64_t *id_h)
{

	struct _fpga_token  *_token;
	struct _fpga_handle *_handle = (struct _fpga_handle *)handle;
	fpga_result result = FPGA_OK;
	fpga_guid guid;

	_token = (struct _fpga_token *)_handle->token;
	if (!_token) {
		OPAE_MSG("Token is NULL");
		return FPGA_INVALID_PARAM;
	}

	if (_token->magic != FPGA_TOKEN_MAGIC) {
		OPAE_MSG("Invalid token in handle");
		return FPGA_INVALID_PARAM;
	}

	if (id_l == NULL || id_h == NULL) {
		OPAE_MSG("id_l or id_h are NULL");
		return FPGA_INVALID_PARAM;
	}

	// PR Interface id
	result = sysfs_get_interface_id(_token, guid);
	if (FPGA_OK != result) {
		OPAE_ERR("Failed to get PR interface id");
		return FPGA_EXCEPTION;
	}

	memcpy(id_h, guid, sizeof(uint64_t));
	*id_h = int64_be_to_le(*id_h);

	memcpy(id_l, guid + sizeof(uint64_t), sizeof(uint64_t));
	*id_l = int64_be_to_le(*id_l);

	return FPGA_OK;
}

fpga_result check_interface_id(fpga_handle handle,
				uint32_t bitstream_magic_no,
				uint64_t ifid_l, uint64_t ifid_h)
{
	uint64_t intfc_id_l = 0;
	uint64_t intfc_id_h = 0;
	fpga_result result  = FPGA_OK;

	if (bitstream_magic_no != FPGA_GBS_6_3_0_MAGIC) {
		OPAE_MSG("Invalid bitstream magic number");
		return FPGA_NOT_FOUND;
	}

	if (get_interface_id(handle, &intfc_id_l, &intfc_id_h)) {
		OPAE_MSG("Get interface ID failed");
		return FPGA_NOT_FOUND;
	}

	if ((ifid_l != intfc_id_l) ||
		(ifid_h != intfc_id_h)) {
		OPAE_MSG("Interface id doesn't match metadata");
		return FPGA_NOT_FOUND;
	}

	return result;
}

fpga_result check_bitstream_guid(const uint8_t *bitstream)
{
	fpga_guid bitstream_guid;
	fpga_guid expected_guid;

	memcpy(bitstream_guid, bitstream, sizeof(fpga_guid));

	if (string_to_guid(METADATA_GUID, &expected_guid) != FPGA_OK)
		return FPGA_INVALID_PARAM;

	if (uuid_compare(bitstream_guid, expected_guid) != 0)
		return FPGA_INVALID_PARAM;

	return FPGA_OK;
}

int get_bitstream_header_len(const uint8_t *bitstream)
{
	uint32_t json_len = 0;

	if (!bitstream) {
		OPAE_ERR("NULL input bitstream pointer");
		return -1;
	}

	if (check_bitstream_guid(bitstream) != FPGA_OK)
		return -1;

	json_len = read_int_from_bitstream(bitstream + METADATA_GUID_LEN, sizeof(uint32_t));

	return (METADATA_GUID_LEN + sizeof(uint32_t) + json_len);
}

int32_t get_bitstream_json_len(const uint8_t *bitstream)
{
	uint32_t json_len = 0;

	if (!bitstream) {
		OPAE_ERR("NULL input bitstream pointer");
		return -1;
	}

	if (check_bitstream_guid(bitstream) != FPGA_OK)
		return -1;

	json_len = read_int_from_bitstream(bitstream + METADATA_GUID_LEN, sizeof(uint32_t));

	return json_len;
}

fpga_result validate_bitstream_metadata(fpga_handle handle,
			const uint8_t *bitstream)
{
	fpga_result result = FPGA_EXCEPTION;
	char *json_metadata = NULL;
	uint32_t json_len = 0;
	uint32_t bitstream_magic_no = 0;
	uint64_t ifc_id_val_l, ifc_id_val_h;
	const uint8_t *json_metadata_ptr = NULL;
	json_object *root = NULL;
	json_object *afu_image = NULL, *magic_no = NULL;
	json_object *interface_id = NULL;
	fpga_guid expected_guid;

	if (check_bitstream_guid(bitstream) != FPGA_OK)
		goto out_free;

	json_len = read_int_from_bitstream(bitstream + METADATA_GUID_LEN, sizeof(uint32_t));
	if (json_len == 0) {
		OPAE_MSG("Bitstream has no metadata");
		result = FPGA_OK;
		goto out_free;
	}

	if (json_len >= METADATA_MAX_LEN) {
		OPAE_ERR("Bitstream metadata too large");
		goto out_free;
	}

	json_metadata_ptr = bitstream + METADATA_GUID_LEN + sizeof(uint32_t);

	json_metadata = (char *) malloc(json_len + 1);
	if (json_metadata == NULL) {
		OPAE_ERR("Could not allocate memory for metadata");
		return FPGA_NO_MEMORY;
	}

	memcpy(json_metadata, json_metadata_ptr, json_len);
	json_metadata[json_len] = '\0';

	root = json_tokener_parse(json_metadata);

	if (root != NULL) {
		if (get_json_object(&afu_image, &root, GBS_AFU_IMAGE)) {
			get_json_object(&magic_no, &afu_image, GBS_MAGIC_NUM);
			get_json_object(&interface_id, &afu_image,
					BBS_INTERFACE_ID);

			if (magic_no == NULL || interface_id == NULL) {
				OPAE_ERR("Invalid metadata");
				result = FPGA_INVALID_PARAM;
				goto out_free;
			}

			result = string_to_guid(
				json_object_get_string(interface_id),
				&expected_guid);
			if (result != FPGA_OK) {
				OPAE_ERR("Invalid BBS interface ID");
				goto out_free;
			}

			memcpy(&ifc_id_val_h, expected_guid, sizeof(uint64_t));
			ifc_id_val_h = int64_be_to_le(ifc_id_val_h);

			memcpy(&ifc_id_val_l,
				expected_guid + sizeof(uint64_t),
				sizeof(uint64_t));
			ifc_id_val_l = int64_be_to_le(ifc_id_val_l);

			bitstream_magic_no = json_object_get_int(magic_no);

			result = check_interface_id(handle, bitstream_magic_no,
						ifc_id_val_l, ifc_id_val_h);

			if (result != FPGA_OK) {
				OPAE_ERR("Interface ID check failed");
				goto out_free;
			}
		} else {
			OPAE_ERR("Invalid metadata");
			result = FPGA_INVALID_PARAM;
			goto out_free;
		}
	}

out_free:
	if (root)
		json_object_put(root);
	if (json_metadata)
		free(json_metadata);

	return result;
}

fpga_result read_gbs_metadata(const uint8_t *bitstream,
				struct gbs_metadata *gbs_metadata)
{
	uint32_t json_len                   = 0;
	fpga_result result                  = FPGA_OK;
	const uint8_t *json_metadata_ptr    = NULL;
	char *json_metadata                 = NULL;
	json_object *root                   = NULL;
	json_object *magic_num              = NULL;
	json_object *interface_id           = NULL;
	json_object *afu_image              = NULL;
	json_object *version                = NULL;
	json_object *accelerator_clusters   = NULL;
	json_object *cluster                = NULL;
	json_object *uuid                   = NULL;
	json_object *name                   = NULL;
	json_object *contexts               = NULL;
	json_object *power                  = NULL;
	json_object *userclk1               = NULL;
	json_object *userclk2               = NULL;

	if (gbs_metadata == NULL) {
		OPAE_ERR("Invalid input metadata");
		return FPGA_INVALID_PARAM;
	}

	if (bitstream == NULL) {
		OPAE_ERR("Invalid input bitstream");
		return FPGA_INVALID_PARAM;
	}

	if (check_bitstream_guid(bitstream) != FPGA_OK) {
		OPAE_ERR("Failed to read GUID");
		return FPGA_INVALID_PARAM;
	}

	json_len = *((uint32_t *) (bitstream + METADATA_GUID_LEN));
	if (!json_len || json_len >= METADATA_MAX_LEN) {
		OPAE_ERR("Invalid bitstream metadata size");
		return FPGA_INVALID_PARAM;
	}

	json_metadata_ptr = bitstream + METADATA_GUID_LEN + sizeof(uint32_t);

	json_metadata = (char *) malloc(json_len + 1);
	if (!json_metadata) {
		OPAE_ERR("Could not allocate memory for metadata");
		return FPGA_NO_MEMORY;
	}

	memcpy(json_metadata, json_metadata_ptr, json_len);
	json_metadata[json_len] = '\0';

	root = json_tokener_parse(json_metadata);

	if (root) {

		// GBS version
		if (get_json_object(&version, &root, GBS_VERSION)) {
			gbs_metadata->version = json_object_get_double(version);
		} else {
			OPAE_ERR("No GBS version");
			result = FPGA_INVALID_PARAM;
			goto out_free;
		}

		// afu-image
		if (get_json_object(&afu_image, &root, GBS_AFU_IMAGE)) {

			// magic number
			if (get_json_object(&magic_num, &afu_image, GBS_MAGIC_NUM)) {
				gbs_metadata->afu_image.magic_num = json_object_get_int64(magic_num);
			}

			// Interface type GUID
			if (get_json_object(&interface_id, &afu_image, BBS_INTERFACE_ID)) {
				memcpy(gbs_metadata->afu_image.interface_uuid,
						json_object_get_string(interface_id),
						GUID_LEN);
				gbs_metadata->afu_image.interface_uuid[GUID_LEN] = '\0';
			} else {
				OPAE_ERR("No interface ID found in JSON metadata");
				result = FPGA_INVALID_PARAM;
				goto out_free;
			}

			// AFU user clock frequency High
			if (get_json_object(&userclk1, &afu_image, GBS_CLOCK_FREQUENCY_HIGH)) {
				gbs_metadata->afu_image.clock_frequency_high = json_object_get_int64(userclk1);
			}

			// AFU user clock frequency Low
			if (get_json_object(&userclk2, &afu_image, GBS_CLOCK_FREQUENCY_LOW)) {
				gbs_metadata->afu_image.clock_frequency_low = json_object_get_int64(userclk2);
			}

			// GBS power
			if (get_json_object(&power, &afu_image, GBS_AFU_POWER)) {
				gbs_metadata->afu_image.power = json_object_get_int64(power);
			}

		} else {
			OPAE_ERR("No AFU image in metadata");
			result = FPGA_INVALID_PARAM;
			goto out_free;
		}

		// afu clusters
		if (get_json_object(&afu_image, &root, GBS_AFU_IMAGE) &&
			get_json_object(&accelerator_clusters, &afu_image, GBS_ACCELERATOR_CLUSTERS)) {

			cluster = json_object_array_get_idx(accelerator_clusters, 0);

			// AFU GUID
			if (get_json_object(&uuid, &cluster, GBS_ACCELERATOR_TYPE_UUID)) {
				memcpy(gbs_metadata->afu_image.afu_clusters.afu_uuid,
						json_object_get_string(uuid),
						GUID_LEN);
				gbs_metadata->afu_image.afu_clusters.afu_uuid[GUID_LEN] = '\0';
			} else {
				OPAE_ERR("No accelerator-type-uuid in JSON metadata");
				result = FPGA_INVALID_PARAM;
				goto out_free;
			}

			// AFU Name
			if (get_json_object(&name, &cluster, GBS_AFU_NAME)) {
				memcpy(gbs_metadata->afu_image.afu_clusters.name,
						json_object_get_string(name),
						json_object_get_string_len(name));
			}

			// AFU Total number of contexts
			if (get_json_object(&contexts, &cluster, GBS_ACCELERATOR_TOTAL_CONTEXTS)) {
				gbs_metadata->afu_image.afu_clusters.total_contexts = json_object_get_int64(contexts);
			}

		} else {
			OPAE_ERR("No accelerator clusters in metadata");
			result = FPGA_INVALID_PARAM;
			goto out_free;
		}
	} else {
		OPAE_ERR("Invalid JSON in metadata");
		result = FPGA_INVALID_PARAM;
		goto out_free;
	}

out_free:
	if (root)
		json_object_put(root);
	if (json_metadata)
		free(json_metadata);

	return result;
}
