/* Copyright 2013-2014 IBM Corp.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * 	http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "hdif.h"
#include <stack.h>

const void *HDIF_get_idata(const struct HDIF_common_hdr *hdif, unsigned int di,
			   unsigned int *size)
{
	const struct HDIF_common_hdr *hdr = hdif;
	const struct HDIF_idata_ptr *iptr;

	if (be16_to_cpu(hdr->d1f0) != 0xd1f0) {
		prerror("HDIF: Bad header format !\n");
		backtrace();
		return NULL;
	}

	if (di >= be16_to_cpu(hdr->idptr_count)) {
		prlog(PR_DEBUG, "HDIF: idata %d out of range for %.6s!\n",
			di, hdr->id);
		return NULL;
	}

	iptr = (void *)hdif + be32_to_cpu(hdr->idptr_off)
		+ di * sizeof(struct HDIF_idata_ptr);

	if (size)
		*size = be32_to_cpu(iptr->size);

	return (void *)hdif + be32_to_cpu(iptr->offset);
}

const void *HDIF_get_iarray_item(const struct HDIF_common_hdr *hdif,
				 unsigned int di, unsigned int ai,
				 unsigned int *size)
{
	const struct HDIF_array_hdr *ahdr;
	unsigned int asize;
	const void *arr;

	arr = HDIF_get_idata(hdif, di, &asize);
	if (!arr)
		return NULL;

	if (asize < sizeof(struct HDIF_array_hdr)) {
		prerror("HDIF: idata block too small for array !\n");
		backtrace();
		return NULL;
	}

	ahdr = arr;

	if (ai >= be32_to_cpu(ahdr->ecnt)) {
		prerror("HDIF: idata array index out of range !\n");
		backtrace();
		return NULL;
	}

	if (size)
		*size = be32_to_cpu(ahdr->eactsz);

	return arr + be32_to_cpu(ahdr->offset) + ai * be32_to_cpu(ahdr->esize);
}

int HDIF_get_iarray_size(const struct HDIF_common_hdr *hdif, unsigned int di)
{
	const struct HDIF_array_hdr *ahdr;
	unsigned int asize;
	const void *arr;

	arr = HDIF_get_idata(hdif, di, &asize);
	if (!arr)
		return -1;

	if (asize < sizeof(struct HDIF_array_hdr)) {
		prerror("HDIF: idata block too small for array !\n");
		backtrace();
		return -1;
	}

	ahdr = arr;
	return be32_to_cpu(ahdr->ecnt);
}

/*
 * Returns NULL and sets *items to zero when:
 *
 * a) Array extends beyond bounds (hard error)
 * b) The array is empty (soft error)
 * c) The item size is zero (soft error)
 * d) The array is missing (soft error)
 *
 * b, c) are bugs in the input data so they generate backtraces.
 *
 * If you care about the soft error cases, retrive the array header manually
 * with HDIF_get_idata().
 */
const struct HDIF_array_hdr *HDIF_get_iarray(const struct HDIF_common_hdr *hdif,
				unsigned int di, unsigned int *items)
{
	const struct HDIF_array_hdr *arr;
	unsigned int req_size, size, elements;
	unsigned int actual_sz, alloc_sz, offset;

	arr = HDIF_get_idata(hdif, di, &size);

	if(items)
		*items = 0;

	if (!arr || !size)
		return NULL;

	/* base size of an Idata array header */
	offset = be32_to_cpu(arr->offset);
	actual_sz = be32_to_cpu(arr->eactsz);
	alloc_sz = be32_to_cpu(arr->esize);
	elements = be32_to_cpu(arr->ecnt);

	/* actual size should always be smaller than allocated */
	if (alloc_sz < actual_sz) {
		prerror("HDIF %.6s iarray %u has actsz (%u) < alloc_sz (%u)\n)",
			hdif->id, di, actual_sz, alloc_sz);
		backtrace();
		return NULL;
	}

	req_size = elements * alloc_sz + offset;
	if (req_size > size) {
		prerror("HDIF: %.6s iarray %u requires %#x bytes, but only %#x are allocated!\n",
			hdif->id, di, req_size, size);
		backtrace();
		return NULL;
	}

	if (!elements || !actual_sz)
		return NULL;

	if (items)
		*items = elements;

	return arr;
}

const void *HDIF_iarray_item(const struct HDIF_array_hdr *ahdr,
				unsigned int index)
{
	if (!ahdr || index >= be32_to_cpu(ahdr->ecnt))
		return NULL;

	return (const void * )ahdr + be32_to_cpu(ahdr->offset) +
			index * be32_to_cpu(ahdr->esize);
}

struct HDIF_child_ptr *
HDIF_child_arr(const struct HDIF_common_hdr *hdif, unsigned int idx)
{
	struct HDIF_child_ptr *children;

	children = (void *)hdif + be32_to_cpu(hdif->child_off);

	if (idx >= be16_to_cpu(hdif->child_count)) {
		prerror("HDIF: child array idx out of range!\n");
		backtrace();
		return NULL;
	}

	return &children[idx];
}

struct HDIF_common_hdr *HDIF_child(const struct HDIF_common_hdr *hdif,
				   const struct HDIF_child_ptr *child,
				   unsigned int idx,
				   const char *eyecatcher)
{
	void *base = (void *)hdif;
	struct HDIF_common_hdr *ret;
	long child_off;

	/* child must be in hdif's child array */
	child_off = (void *)child - (base + be32_to_cpu(hdif->child_off));
	assert(child_off % sizeof(struct HDIF_child_ptr) == 0);
	assert(child_off / sizeof(struct HDIF_child_ptr)
	       < be16_to_cpu(hdif->child_count));

	assert(idx < be32_to_cpu(child->count));

	if (be32_to_cpu(child->size) < sizeof(struct HDIF_common_hdr)) {
		prerror("HDIF: %s child #%i too small: %u\n",
			eyecatcher, idx, be32_to_cpu(child->size));
		backtrace();
		return NULL;
	}

	ret = base + be32_to_cpu(child->offset)
		+ be32_to_cpu(child->size) * idx;
	if (!HDIF_check(ret, eyecatcher)) {
		prerror("HDIF: #%i bad type (wanted %6s, got %6s)\n",
			idx, eyecatcher, ret->id);
		backtrace();
		return NULL;
	}

	return ret;
}
