// Copyright(c) 2018-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 <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include <uuid/uuid.h>
#include <json-c/json.h>
#include "bitstream.h"
#include "bits_utils.h"
#include "metadatav1.h"

#include <opae/log.h>
#include <opae/properties.h>
#include <opae/sysobject.h>

STATIC fpga_result opae_bitstream_read_file(const char *file,
					    uint8_t **buf,
					    size_t *len)
{
	FILE *fp;
	fpga_result res = FPGA_EXCEPTION;
	long pos;
	size_t sz;

	fp = fopen(file, "rb");
	if (!fp) {
		OPAE_ERR("fopen failed");
		return FPGA_EXCEPTION;
	}

	if (fseek(fp, 0, SEEK_END) < 0) {
		OPAE_ERR("fseek failed");
		goto out_close;
	}

	pos = ftell(fp);
	if (pos < 0) {
		OPAE_ERR("ftell failed");
		goto out_close;
	}

	*len = (size_t)pos;

	*buf = (uint8_t *)malloc(*len);
	if (!*buf) {
		OPAE_ERR("malloc failed");
		res = FPGA_NO_MEMORY;
		*len = 0;
		goto out_close;
	}

	if (fseek(fp, 0, SEEK_SET) < 0) {
		OPAE_ERR("fseek failed");
		goto out_free;
	}

	sz = fread(*buf, 1, *len, fp);
	if (ferror(fp)) {
		OPAE_ERR("ferror after read");
		goto out_free;
	}

	if (sz != *len) {
		OPAE_ERR("file size and number "
			 "of bytes read mismatch");
		goto out_free;
	}

	fclose(fp);
	return FPGA_OK;

out_free:
	free(*buf);
	*buf = NULL;
	*len = 0;
out_close:
	fclose(fp);
	return res;
}

bool opae_is_legacy_bitstream(opae_bitstream_info *info)
{
	opae_legacy_bitstream_header *hdr;

	if (info->data_len < sizeof(opae_legacy_bitstream_header))
		return false;

	hdr = (opae_legacy_bitstream_header *)info->data;
	if (hdr->legacy_magic == OPAE_LEGACY_BITSTREAM_MAGIC)
		return true;

	return false;
}

STATIC void opae_resolve_legacy_bitstream(opae_bitstream_info *info)
{
	opae_legacy_bitstream_header *hdr =
		(opae_legacy_bitstream_header *)info->data;
	uint8_t *p = &hdr->legacy_pr_ifc_id[15];
	int i = 0;

	// The guid is encoded backwards.
	// Reverse it.
	while (p >= hdr->legacy_pr_ifc_id) {
		info->pr_interface_id[i++] = *p--;
	}

	info->rbf_data = info->data + sizeof(opae_legacy_bitstream_header);
	info->rbf_len = info->data_len - sizeof(opae_legacy_bitstream_header);
}

STATIC void *opae_bitstream_parse_metadata(const char *metadata,
					   fpga_guid pr_interface_id,
					   int *version)
{
	json_object *root = NULL;
	json_object *j_version = NULL;
	enum json_tokener_error j_err = json_tokener_success;
	void *parsed = NULL;

	root = json_tokener_parse_verbose(metadata, &j_err);
	if (!root) {
		OPAE_ERR("invalid JSON metadata: %s",
			 json_tokener_error_desc(j_err));
		return NULL;
	}

	if (!json_object_object_get_ex(root,
				       "version",
				       &j_version)) {
		OPAE_ERR("metadata: failed to find \"version\" key");
		goto out_put;
	}

	if (!json_object_is_type(j_version, json_type_int)) {
		OPAE_ERR("metadata: \"version\" key not integer");
		goto out_put;
	}

	*version = json_object_get_int(j_version);

	switch (*version) {

	// Some invalid GBS's around the BBS 6.4.0 and
	// BBS 6.5.0 eras incorrectly set the metadata
	// version to 640/650 respectively.
	// Allow 640/650 to serve as an alias for 1.
	case 650:
	case 640:
		*version = 1; /* FALLTHROUGH */
	case 1:
		parsed = opae_bitstream_parse_metadata_v1(root,
							  pr_interface_id);
	break;

	default:
		OPAE_ERR("metadata: unsupported version: %d", *version);
	}

out_put:
	json_object_put(root);

	return parsed;
}

STATIC fpga_guid valid_GBS_guid = {
0x58, 0x65, 0x6f, 0x6e,
0x46, 0x50,
0x47, 0x41,
0xb7, 0x47,
0x42, 0x53, 0x76, 0x30, 0x30, 0x31
};
STATIC fpga_result opae_resolve_bitstream(opae_bitstream_info *info)
{
	opae_bitstream_header *hdr;
	size_t sz;
	char *buf;

	if (info->data_len < sizeof(opae_bitstream_header)) {
		OPAE_ERR("file length smaller than bitstream header: "
			 "\"%s\"", info->filename);
		return FPGA_INVALID_PARAM;
	}

	hdr = (opae_bitstream_header *)info->data;

	if (uuid_compare(hdr->valid_gbs_guid, valid_GBS_guid) != 0) {
		OPAE_ERR("GBS guid is invalid: \"%s\"", info->filename);
		return FPGA_INVALID_PARAM;
	}

	// Check that metadata_length makes sense
	// given that we know the total file size.

	sz = sizeof(fpga_guid) + sizeof(uint32_t);
	sz += (size_t)hdr->metadata_length;

	if (sz > info->data_len) {
		OPAE_ERR("invalid metadata length in \"%s\"", info->filename);
		return FPGA_INVALID_PARAM;
	}

	info->rbf_data = info->data + sz;
	info->rbf_len = info->data_len - sz;

	buf = (char *)malloc(hdr->metadata_length + 1);
	if (!buf) {
		OPAE_ERR("malloc failed");
		return FPGA_NO_MEMORY;
	}

	memcpy(buf, hdr->metadata, hdr->metadata_length);
	buf[hdr->metadata_length] = '\0';

	info->parsed_metadata =
		opae_bitstream_parse_metadata(buf,
					      info->pr_interface_id,
					      &info->metadata_version);

	free(buf);

	return info->parsed_metadata ? FPGA_OK : FPGA_EXCEPTION;
}

fpga_result opae_load_bitstream(const char *file, opae_bitstream_info *info)
{
	fpga_result res;

	if (!file || !info)
		return FPGA_INVALID_PARAM;

	if (!opae_bitstream_path_is_valid(file,
					  OPAE_BITSTREAM_PATH_NO_SYMLINK)) {
		OPAE_ERR("invalid bitstream path \"%s\"", file);
		return FPGA_INVALID_PARAM;
	}

	memset(info, 0, sizeof(opae_bitstream_info));

	res = opae_bitstream_read_file(file, &info->data, &info->data_len);
	if (res != FPGA_OK) {
		OPAE_ERR("error loading \"%s\"", file);
		return res;
	}

	info->filename = file;

	if (opae_is_legacy_bitstream(info)) {
		opae_resolve_legacy_bitstream(info);
		OPAE_MSG("Legacy bitstream (GBS) format detected.");
		OPAE_MSG("Legacy GBS support is deprecated "
			 "and will be removed in a future release.");
		return FPGA_OK;
	}

	return opae_resolve_bitstream(info);
}

fpga_result opae_unload_bitstream(opae_bitstream_info *info)
{
	fpga_result res = FPGA_OK;

	if (!info)
		return FPGA_INVALID_PARAM;

	if (info->data)
		free(info->data);

	if (info->parsed_metadata) {

		switch (info->metadata_version) {

		case 1:
			opae_bitstream_release_metadata_v1(
			(opae_bitstream_metadata_v1 *)info->parsed_metadata);
		break;

		default:
			OPAE_ERR("metadata: unsupported version: %d",
				 info->metadata_version);
			res = FPGA_EXCEPTION;
		}

	}

	memset(info, 0, sizeof(opae_bitstream_info));

	return res;
}
