/*
  Copyright (c) 2019 Red Hat Inc.
  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 program 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.

  Authors: Jaroslav Kysela <perex@perex.cz>
*/

#include "tplg_local.h"

int tplg_decode_template(snd_tplg_t *tplg,
			 size_t pos,
			 struct snd_soc_tplg_hdr *hdr,
			 snd_tplg_obj_template_t *t)
{
	int type;

	type = tplg_get_type(hdr->type);
	tplg_log(tplg, 'D', pos, "template: asoc type %d library type %d",
		 hdr->type, type);
	if (type < 0)
		return type;

	memset(t, 0, sizeof(*t));
	t->type = type;
	t->index = hdr->index;
	t->version = hdr->version;
	t->vendor_type = hdr->vendor_type;
	tplg_log(tplg, 'D', pos, "template: index %d version %d vendor_type %d",
		 hdr->index, hdr->version, hdr->vendor_type);
	return 0;
}

int snd_tplg_decode(snd_tplg_t *tplg, void *bin, size_t size, int dflags)
{
	struct snd_soc_tplg_hdr *hdr;
	struct tplg_table *tptr;
	size_t pos;
	void *b = bin;
	unsigned int index;
	int err;

	if (dflags != 0)
		return -EINVAL;
	if (tplg == NULL || bin == NULL)
		return -EINVAL;
	while (1) {
		pos = b - bin;
		if (size == pos) {
			tplg_log(tplg, 'D', pos, "block: success (total %zd)", size);
			return 0;
		}
		if (size - pos < sizeof(*hdr)) {
			tplg_log(tplg, 'D', pos, "block: small size");
			snd_error(TOPOLOGY, "incomplete header data to decode");
			return -EINVAL;
		}
		hdr = b;
		if (hdr->magic != SND_SOC_TPLG_MAGIC) {
			snd_error(TOPOLOGY, "bad block magic %08x", hdr->magic);
			return -EINVAL;
		}

		tplg_log(tplg, 'D', pos, "block: abi %d size %d payload size %d",
			 hdr->abi, hdr->size, hdr->payload_size);
		if (hdr->abi != SND_SOC_TPLG_ABI_VERSION) {
			snd_error(TOPOLOGY, "unsupported ABI version %d", hdr->abi);
			return -EINVAL;
		}
		if (hdr->size != sizeof(*hdr)) {
			snd_error(TOPOLOGY, "header size mismatch");
			return -EINVAL;
		}

		if (size - pos < hdr->size + hdr->payload_size) {
			snd_error(TOPOLOGY, "incomplete payload data to decode");
			return -EINVAL;
		}

		if (hdr->payload_size < 8) {
			snd_error(TOPOLOGY, "wrong payload size %d", hdr->payload_size);
			return -EINVAL;
		}

		/* first block must be manifest */
		if (b == bin) {
			if (hdr->type != SND_SOC_TPLG_TYPE_MANIFEST) {
				snd_error(TOPOLOGY, "first block must be manifest (value %d)", hdr->type);
				return -EINVAL;
			}
			err = snd_tplg_set_version(tplg, hdr->version);
			if (err < 0)
				return err;
		}

		pos += hdr->size;
		for (index = 0; index < tplg_table_items; index++) {
			tptr = &tplg_table[index];
			if (tptr->tsoc == (int)hdr->type)
				break;
		}
		if (index >= tplg_table_items || tptr->decod == NULL) {
			snd_error(TOPOLOGY, "unknown block type %d", hdr->type);
			return -EINVAL;
		}
		tplg_log(tplg, 'D', pos, "block: type %d - %s", hdr->type, tptr->name);
		err = tptr->decod(tplg, pos, hdr, b + hdr->size, hdr->payload_size);
		if (err < 0)
			return err;
		b += hdr->size + hdr->payload_size;
	}
}
