/*
 * fls.c
 * support for .fls file format (found in .bbfw files)
 *
 * Copyright (c) 2012 Nikias Bassen. All Rights Reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "fls.h"
#include "common.h"

#ifndef offsetof
#define offsetof(type, member)  __builtin_offsetof (type, member)
#endif

static void fls_parse_elements(fls_file* fls)
{
	/* FIXME: the following code is not big endian safe */
	if (!fls || !fls->data) {
		return;
	}
	uint32_t offset = 0;
	fls->max_elements = 32;
	fls->elements = (fls_element**)malloc(sizeof(fls_element*) * fls->max_elements);

	fls_element* cur = NULL;
	do {
		void* p = fls->data + offset;
		uint32_t hdrsize = 0;
		cur = (fls_element*)p;
		if ((offset + cur->size) > fls->size) {
			break;
		}
		fls_element* ne;
		switch (cur->type) {
		case 0x0c:
			{
			hdrsize = offsetof(fls_0c_element, data);
			fls_0c_element* xe = (fls_0c_element*)malloc(sizeof(fls_0c_element));
			memset(xe, '\0', sizeof(fls_0c_element));
			memcpy((void*)xe, p, hdrsize);
			xe->data = (xe->size > hdrsize) ? p + hdrsize : NULL;
			ne = (fls_element*)xe;
			fls->c_element = xe;
			}
			break;
		case 0x10:
			{
			hdrsize = offsetof(fls_10_element, data);
			fls_10_element* xe = (fls_10_element*)malloc(sizeof(fls_10_element));
			memset(xe, '\0', sizeof(fls_10_element));
			memcpy((void*)xe, p, hdrsize);
			xe->data = (xe->size > hdrsize) ? p + hdrsize : NULL;
			ne = (fls_element*)xe;
			}
			break;
		case 0x14:
			{
			hdrsize = offsetof(fls_14_element, data);
			fls_14_element* xe = (fls_14_element*)malloc(sizeof(fls_14_element));
			memset(xe, '\0', sizeof(fls_14_element));
			memcpy((void*)xe, p, hdrsize);
			xe->data = (xe->size > hdrsize) ? p + hdrsize : NULL;
			ne = (fls_element*)xe;
			}
			break;
		default:
			hdrsize = offsetof(fls_element, data);
			ne = (fls_element*)malloc(sizeof(fls_element));
			memset(ne, '\0', sizeof(fls_element));
			ne->type = cur->type;
			ne->size = cur->size;
			ne->data = (ne->size > hdrsize) ? p + hdrsize : NULL;
			break;
		}
		if ((fls->num_elements + 1) > fls->max_elements) {
			fls->max_elements += 10;
			fls->elements = (fls_element**)realloc(fls->elements, sizeof(fls_element*) * fls->max_elements);
		}
		fls->elements[fls->num_elements++] = ne;
		offset += cur->size;
	} while (offset < fls->size);
	if (offset != fls->size) {
		error("ERROR: %s: error parsing elements\n", __func__);
		return;
	}
}

fls_file* fls_parse(unsigned char* data, unsigned int size)
{
	fls_file* fls = (fls_file*)malloc(sizeof(fls_file));
	if (!fls) {
		return NULL;
	}
	memset(fls, '\0', sizeof(fls_file));
	fls->data = malloc(size);
	fls->size = size;
	memcpy(fls->data, data, size);
	fls_parse_elements(fls);
	return fls;
}

void fls_free(fls_file* fls)
{
	if (fls) {
		if (fls->num_elements > 0) {
			int i;
			for (i = fls->num_elements-1; i >=0; i--) {
				free(fls->elements[i]);
			}
			free(fls->elements);
		}
		if (fls->data) {
			free(fls->data);
		}
		free(fls);
	}
}

int fls_update_sig_blob(fls_file* fls, const unsigned char* sigdata, unsigned int siglen)
{
	/* FIXME: the code in this function is not big endian safe */
	if (!fls || !fls->num_elements) {
		error("ERROR: %s: no data\n", __func__);
		return -1;
	}
	if (!fls->c_element) {
		error("ERROR: %s: no fls_0c_element in fls data\n", __func__);
		return -1;
	}

	uint32_t datasize = *(uint32_t*)(fls->c_element->data + 0x10);
	if (datasize != fls->c_element->data_size) {
		error("ERROR: %s: data size mismatch (0x%x != 0x%x)\n", __func__, datasize, fls->c_element->data_size);
		return -1;
	}
	uint32_t sigoffset = *(uint32_t*)(fls->c_element->data + 0x14);
	if (sigoffset > datasize) {
		error("ERROR: %s: signature offset greater than data size (0x%x > 0x%x)\n", __func__, sigoffset, datasize);
		return -1;
	}

	uint32_t oldsiglen = datasize - sigoffset;
	uint32_t newsize = fls->size - oldsiglen + siglen;

	unsigned int i;
	uint32_t offset = 0;
	void* newdata = malloc(newsize);
	if (!newdata) {
		error("ERROR: %s: out of memory\n", __func__);
		return -1;
	}
	uint32_t hdrsize = 0;
	uint32_t firstpartlen = 0;
	for (i = 0; i < fls->num_elements; i++) {
		switch (fls->elements[i]->type) {
		case 0x0c:
			hdrsize = offsetof(fls_0c_element, data);
			// update offset
			((fls_0c_element*)fls->elements[i])->offset = offset+hdrsize;
			// copy first part of data
			firstpartlen = fls->elements[i]->size - hdrsize - oldsiglen;
			memcpy(newdata+offset+hdrsize, ((fls_0c_element*)fls->elements[i])->data, firstpartlen);
			// copy new signature data
			memcpy(newdata+offset+hdrsize+firstpartlen, sigdata, siglen);
			((fls_0c_element*)fls->elements[i])->data = newdata+offset+hdrsize;
			fls->elements[i]->size -= oldsiglen;
			fls->elements[i]->size += siglen;
			((fls_0c_element*)fls->elements[i])->data_size -= oldsiglen;
			((fls_0c_element*)fls->elements[i])->data_size += siglen;
			memcpy(newdata+offset+hdrsize+0x10, &(((fls_0c_element*)fls->elements[i])->data_size), 4);
			// copy header
			memcpy(newdata+offset, fls->elements[i], hdrsize);
			break;
		case 0x10:
			hdrsize = offsetof(fls_10_element, data);
			// update offset
			((fls_10_element*)fls->elements[i])->offset = offset+hdrsize;
			// copy header
			memcpy(newdata+offset, fls->elements[i], hdrsize);
			// copy data
			if (fls->elements[i]->size > hdrsize) {
				memcpy(newdata+offset+hdrsize, ((fls_10_element*)fls->elements[i])->data, fls->elements[i]->size - hdrsize);
				((fls_10_element*)fls->elements[i])->data = newdata+offset+hdrsize;
			} else {
				((fls_10_element*)fls->elements[i])->data = NULL;
			}
			break;
		case 0x14:
			hdrsize = offsetof(fls_14_element, data);
			// update offset
			((fls_14_element*)fls->elements[i])->offset = offset+hdrsize;
			// copy header
			memcpy(newdata+offset, fls->elements[i], hdrsize);
			// copy data
			if (fls->elements[i]->size > hdrsize) {
				memcpy(newdata+offset+hdrsize, ((fls_14_element*)fls->elements[i])->data, fls->elements[i]->size - hdrsize);
				((fls_14_element*)fls->elements[i])->data = newdata+offset+hdrsize;
			} else {
				((fls_14_element*)fls->elements[i])->data = NULL;
			}
			break;
		default:
			hdrsize = offsetof(fls_element, data);
			// copy header
			memcpy(newdata+offset, fls->elements[i], hdrsize);
			// copy data
			if (fls->elements[i]->size > hdrsize) {
				memcpy(newdata+offset+hdrsize, fls->elements[i]->data, fls->elements[i]->size - hdrsize);
				fls->elements[i]->data = newdata+offset+hdrsize;
			} else {
				fls->elements[i]->data = NULL;
			}
			break;
		}
		offset += fls->elements[i]->size;
	}
	if (fls->data) {
		free(fls->data);
	}
	fls->data = newdata;
	fls->size = newsize;

	return 0;
}

int fls_insert_ticket(fls_file* fls, const unsigned char* data, unsigned int size)
{
	/* FIXME: the code in this function is not big endian safe */
	if (!fls || !fls->num_elements) {
		error("ERROR: %s: no data\n", __func__);
		return -1;
	}
	if (!fls->c_element) {
		error("ERROR: %s: no fls_0c_element in fls data\n", __func__);
		return -1;
	}

	uint32_t padding = 0;
	if (size%4 != 0) {
		padding = 4-(size%4);
	}
	uint32_t newsize = fls->size + size + padding;
	unsigned int i;
	uint32_t offset = 0;
	void* newdata = malloc(newsize);
	if (!newdata) {
		error("ERROR: %s: out of memory\n", __func__);
		return -1;
	}
	uint32_t hdrsize = 0;
	for (i = 0; i < fls->num_elements; i++) {
		switch (fls->elements[i]->type) {
		case 0x0c:
			hdrsize = offsetof(fls_0c_element, data);
			// update offset
			((fls_0c_element*)fls->elements[i])->offset = offset+hdrsize;
			// copy ticket data
			memcpy(newdata+offset+hdrsize, data, size);
			if (padding > 0) {
				// padding
				memset(newdata+offset+hdrsize+size, '\xFF', padding);
			}
			// copy remaining data
			memcpy(newdata+offset+hdrsize+size+padding, ((fls_0c_element*)fls->elements[i])->data, fls->elements[i]->size);
			((fls_0c_element*)fls->elements[i])->data = newdata+offset+hdrsize;
			fls->elements[i]->size += (size + padding);
			((fls_0c_element*)fls->elements[i])->data_size += (size + padding);
			// copy header
			memcpy(newdata+offset, fls->elements[i], hdrsize);
			break;
		case 0x10:
			hdrsize = offsetof(fls_10_element, data);
			// update offset
			((fls_10_element*)fls->elements[i])->offset = offset+hdrsize;
			// copy header
			memcpy(newdata+offset, fls->elements[i], hdrsize);
			// copy data
			if (fls->elements[i]->size > hdrsize) {
				memcpy(newdata+offset+hdrsize, ((fls_10_element*)fls->elements[i])->data, fls->elements[i]->size - hdrsize);
				((fls_10_element*)fls->elements[i])->data = newdata+offset+hdrsize;
			} else {
				((fls_10_element*)fls->elements[i])->data = NULL;
			}
			break;
		case 0x14:
			hdrsize = offsetof(fls_14_element, data);
			// update offset
			((fls_14_element*)fls->elements[i])->offset = offset+hdrsize;
			// copy header
			memcpy(newdata+offset, fls->elements[i], hdrsize);
			// copy data
			if (fls->elements[i]->size > hdrsize) {
				memcpy(newdata+offset+hdrsize, ((fls_14_element*)fls->elements[i])->data, fls->elements[i]->size - hdrsize);
				((fls_14_element*)fls->elements[i])->data = newdata+offset+hdrsize;
			} else {
				((fls_14_element*)fls->elements[i])->data = NULL;
			}
			break;
		default:
			hdrsize = offsetof(fls_element, data);
			// copy header
			memcpy(newdata+offset, fls->elements[i], hdrsize);
			// copy data
			if (fls->elements[i]->size > hdrsize) {
				memcpy(newdata+offset+hdrsize, fls->elements[i]->data, fls->elements[i]->size - hdrsize);
				fls->elements[i]->data = newdata+offset+hdrsize;
			} else {
				fls->elements[i]->data = NULL;
			}
			break;
		}
		offset += fls->elements[i]->size;
	}
	if (fls->data) {
		free(fls->data);
	}
	fls->data = newdata;
	fls->size = newsize;

	return 0;
}

