// SPDX-License-Identifier: GPL-2.0-only
// SPDX-FileCopyrightText: 2012 NVIDIA CORPORATION

/*
 * parse.c - Parsing support for the cbootimage tool
 */

#include <ctype.h>
#include "parse.h"
#include "cbootimage.h"
#include "data_layout.h"
#include "crypto.h"
#include "set.h"

/*
 * Function prototypes
 *
 * ParseXXX() parses XXX in the input
 * SetXXX() sets state based on the parsing results but does not perform
 *      any parsing of its own
 * A ParseXXX() function may call other parse functions and set functions.
 * A SetXXX() function may not call any parseing functions.
 */

static int
set_array(build_image_context *context,
			u_int32_t index,
			parse_token token,
			u_int32_t value);
static char *parse_u32(char *str, u_int32_t *val);
static char *parse_u8(char *str, u_int32_t *val);
static char *parse_filename(char *str, char *name, int chars_remaining);
static char *parse_enum(build_image_context *context,
			char *str,
			enum_item *table,
			u_int32_t *val);
static char
*parse_field_name(char *rest, field_item *field_table, field_item **field);
static char
*parse_field_value(build_image_context *context,
			char *rest,
			field_item *field,
			u_int32_t *value);
static int
parse_array(build_image_context *context, parse_token token, char *rest);
static int
parse_bootloader(build_image_context *context, parse_token token, char *rest);
static int
parse_value_u32(build_image_context *context, parse_token token, char *rest);
static int
parse_bct_file(build_image_context *context, parse_token token, char *rest);
static char
*parse_end_state(char *str, char *uname, int chars_remaining);
static int
parse_dev_param(build_image_context *context, parse_token token, char *rest);
static int
parse_sdram_param(build_image_context *context, parse_token token, char *rest);

static int process_statement(build_image_context *context,
				char *str,
				u_int8_t simple_parse);

static parse_item parse_simple_items[] =
{
	{ "Bctfile=",       token_bct_file,		parse_bct_file },
	{ "BootLoader=",    token_bootloader,		parse_bootloader },
	{ "Redundancy=",    token_redundancy,		parse_value_u32 },
	{ "Bctcopy=",       token_bct_copy,		parse_value_u32 },
	{ "Version=",       token_version,		parse_value_u32 },
	{ "PreBctPadBlocks=", token_pre_bct_pad_blocks,	parse_value_u32 },
	{ NULL, 0, NULL } /* Must be last */
};

static parse_item s_top_level_items[] = {
	{ "Bctfile=",       token_bct_file,		parse_bct_file },
	{ "Attribute=",     token_attribute,		parse_value_u32 },
	{ "Attribute[",     token_attribute,		parse_array },
	{ "PageSize=",      token_page_size,		parse_value_u32 },
	{ "BlockSize=",     token_block_size,		parse_value_u32 },
	{ "PartitionSize=", token_partition_size,	parse_value_u32 },
	{ "DevType[",       token_dev_type,		parse_array },
	{ "DeviceParam[",   token_dev_param,		parse_dev_param },
	{ "SDRAM[",         token_sdram,		parse_sdram_param },
	{ "BootLoader=",    token_bootloader,		parse_bootloader },
	{ "Redundancy=",    token_redundancy,		parse_value_u32 },
	{ "Bctcopy=",       token_bct_copy,		parse_value_u32 },
	{ "Version=",       token_version,		parse_value_u32 },
	{ "OdmData=",       token_odm_data,		parse_value_u32 },
	{ NULL, 0, NULL } /* Must be last */
};

/* Macro to simplify parser code a bit. */
#define PARSE_COMMA(x) if (*rest != ',') return (x); rest++

/*
 * Parse the given string and find the u32 dec/hex number.
 *
 * @param str	String to parse
 * @param val	Returns value that was parsed
 * @return the remainder of the string after the number was parsed
 */
static char *
parse_u32(char *str, u_int32_t *val)
{
	u_int32_t value = 0;
	u_int32_t digit;

	while (*str == '0')
		str++;

	if (tolower(*str) == 'x') {
		str++;
		while (isxdigit(*str)) {
			value *= 16;
			digit = tolower(*str);
			value += digit <= '9' ? digit - '0' : digit - 'a' + 10;
			str++;
		}
	} else {
		while (*str >= '0' && *str <= '9') {
			value = value*10 + (*str - '0');
			str++;
		}
	}
	*val = value;
	return str;
}

/*
 * Parse the given string and find the u8 dec/hex number.
 *
 * @param str	String to parse
 * @param val	Returns value that was parsed
 * @return the remainder of the string after the number was parsed
 */
static char *
parse_u8(char *str, u_int32_t *val)
{
	char *retval;

	retval = parse_u32(str, val);

	if (*val > 0xff) {
		printf("Warning: Parsed 8-bit value that exceeded 8-bits.\n");
		printf("         Parsed value = %d. Remaining text = %s\n",
			 *val, retval);
	}

	return retval;
}


/*
 * Parse the given string and find the file name then
 * return the rest of the string.
 *
 * @param str            	String to parse
 * @param name           	Returns the filename that was parsed
 * @param chars_remaining	The maximum length of filename
 * @return the remainder of the string after the name was parsed
 */
static char *
parse_filename(char *str, char *name, int chars_remaining)
{
	/*
	 * Check if the filename buffer is out of space, preserving one
	 * character to null terminate the string.
	 */
	while (isalnum(*str) || strchr("\\/~_-+:.", *str)) {

		chars_remaining--;

		if (chars_remaining < 1)
			return NULL;
		*name++ = *str++;
	}

	/* Null terminate the filename. */
	*name = '\0';

	return str;
}

/*
 * Parse the given string and find the match field name listed
 * in field table.
 *
 * @param rest       	String to parse
 * @param field_table	The field table to parse
 * @param field      	Returns the field item that was parsed
 * @return NULL or the remainder of the string after the field item was parsed
 */
static char
*parse_field_name(char *rest, field_item *field_table, field_item **field)
{
	u_int32_t i;
	u_int32_t field_name_len = 0;

	assert(field_table != NULL);
	assert(rest != NULL);
	assert(field != NULL);

	while (rest[field_name_len] != '=')
		field_name_len++;

	/* Parse the field name. */
	for (i = 0; field_table[i].name != NULL; i++) {
		if ((strlen(field_table[i].name) == field_name_len) &&
			!strncmp(field_table[i].name,
			rest,
			field_name_len)) {

			*field = &(field_table[i]);
			rest = rest + field_name_len;
			return rest;
		}
	}

	/* Field wasn't found or a parse error occurred. */
	return NULL;
}

/*
 * Parse the value based on the field table
 *
 * @param context	The main context pointer
 * @param rest   	String to parse
 * @param field  	Field item to parse
 * @param value	Returns the value that was parsed
 * @return the remainder of the string after the value was parsed
 */
static char
*parse_field_value(build_image_context *context,
			char *rest,
			field_item *field,
			u_int32_t *value)
{
	assert(rest != NULL);
	assert(field != NULL);
	assert((field->type != field_type_enum)
		|| (field->enum_table != NULL));

	switch (field->type) {
	case field_type_enum:
		rest = parse_enum(context, rest, field->enum_table, value);
		break;

	case field_type_u32:
		rest = parse_u32(rest, value);
		break;

	case field_type_u8:
		rest = parse_u8(rest, value);
		break;

	default:
		printf("Unexpected field type %d at line %d\n",
			field->type, __LINE__);
		rest = NULL;
		break;
	}

	return rest;
}

/*
 * Parse the given string and find the match enum item listed
 * in table.
 *
 * @param context	The main context pointer
 * @param str    	String to parse
 * @param table  	Enum item table to parse
 * @param value  	Returns the value that was parsed
 * @return the remainder of the string after the item was parsed
 */
static char *
parse_enum(build_image_context *context,
		char *str,
		enum_item *table,
		u_int32_t *val)
{
	int i;
	char *rest;

	for (i = 0; table[i].name != NULL; i++) {
		if (!strncmp(table[i].name, str,
			strlen(table[i].name))) {
			*val = table[i].value;
			rest = str + strlen(table[i].name);
			return rest;
		}
	}
	return parse_u32(str, val);

}

/*
 * Parse the given string and find the bootloader file name, load address and
 * entry point information then call set_bootloader function.
 *
 * @param context	The main context pointer
 * @param token  	The parse token value
 * @param rest   	String to parse
 * @return 0 and 1 for success and failure
 */
static int parse_bootloader(build_image_context *context,
			parse_token token,
			char *rest)
{
	char filename[MAX_BUFFER];
	char e_state[MAX_STR_LEN];
	u_int32_t load_addr;
	u_int32_t entry_point;

	assert(context != NULL);
	assert(rest != NULL);

	if (context->generate_bct != 0)
		return 0;
	/* Parse the file name. */
	rest = parse_filename(rest, filename, MAX_BUFFER);
	if (rest == NULL)
		return 1;

	PARSE_COMMA(1);

	/* Parse the load address. */
	rest = parse_u32(rest, &load_addr);
	if (rest == NULL)
		return 1;

	PARSE_COMMA(1);

	/* Parse the entry point. */
	rest = parse_u32(rest, &entry_point);
	if (rest == NULL)
		return 1;

	PARSE_COMMA(1);

	/* Parse the end state. */
	rest = parse_end_state(rest, e_state, MAX_STR_LEN);
	if (rest == NULL)
		return 1;
	if (strncmp(e_state, "Complete", strlen("Complete")))
		return 1;

	/* Parsing has finished - set the bootloader */
	return set_bootloader(context, filename, load_addr, entry_point);
}

/*
 * Parse the given string and find the array items in config file.
 *
 * @param context	The main context pointer
 * @param token  	The parse token value
 * @param rest   	String to parse
 * @return 0 and 1 for success and failure
 */
static int
parse_array(build_image_context *context, parse_token token, char *rest)
{
	u_int32_t index;
	u_int32_t value;

	assert(context != NULL);
	assert(rest != NULL);

	/* Parse the index. */
	rest = parse_u32(rest, &index);
	if (rest == NULL)
		return 1;

	/* Parse the closing bracket. */
	if (*rest != ']')
		return 1;
	rest++;

	/* Parse the equals sign.*/
	if (*rest != '=')
		return 1;
	rest++;

	/* Parse the value based on the field table. */
	switch (token) {
	case token_attribute:
		rest = parse_u32(rest, &value);
		break;
	case token_dev_type:
		rest = parse_enum(context,
				rest,
				g_soc_config->devtype_table,
				&value);
		break;

	default:
	/* Unknown token */
		return 1;
	}

	if (rest == NULL)
		return 1;

	/* Store the result. */
	return set_array(context, index, token, value);
}

/*
 * Call hw interface to set the value for array item in bct such as device
 * type and bootloader attribute.
 *
 * @param context	The main context pointer
 * @param index  	The index for array
 * @param token  	The parse token value
 * @param value  	The value to set
 * @return 0 and -ENODATA for success and failure
 */

static int
set_array(build_image_context *context,
			u_int32_t index,
			parse_token token,
			u_int32_t value)
{
	int err = 0;

	assert(context != NULL);

	switch (token) {
	case token_attribute:
		err = g_soc_config->setbl_param(index,
					token_bl_attribute,
					&value,
					context->bct);
		break;
	case token_dev_type:
		err = g_soc_config->set_dev_param(context,
						index,
						token_dev_type,
						value);
		break;
	default:
		break;
	}
	return err;
}

/*
 * General handler for setting u_int32_t values in config files.
 *
 * @param context	The main context pointer
 * @param token  	The parse token value
 * @param rest   	String to parse
 * @return 0 and 1 for success and failure
 */
static int parse_value_u32(build_image_context *context,
			parse_token token,
			char *rest)
{
	u_int32_t value;

	assert(context != NULL);
	assert(rest != NULL);

	rest = parse_u32(rest, &value);
	if (rest == NULL)
		return 1;

	return context_set_value(context, token, value);
}

/*
 * Parse the given string and find the bct file name.
 *
 * @param context	The main context pointer
 * @param token  	The parse token value
 * @param rest   	String to parse
 * @return 0 and 1 for success and failure
 */
static int
parse_bct_file(build_image_context *context, parse_token token, char *rest)
{
	char   filename[MAX_BUFFER];

	assert(context != NULL);
	assert(rest != NULL);

	/* Parse the file name. */
	rest = parse_filename(rest, filename, MAX_BUFFER);
	if (rest == NULL)
		return 1;

	/* Parsing has finished - set the bctfile */
	context->bct_filename = filename;
	/* Read the bct file to buffer */
	if (read_bct_file(context))
		return 1;

	update_context(context);
	return 0;
}

static char *
parse_end_state(char *str, char *uname, int chars_remaining)
{
	while (isalpha(*str)) {

		*uname++ = *str++;
		if (--chars_remaining < 0)
			return NULL;
	}
	*uname = '\0';
	return str;
}

/*
 * Parse the given string and find device parameter listed in device table
 * and value for this device parameter. If match, call the corresponding
 * function in the table to set device parameter.
 *
 * @param context	The main context pointer
 * @param token  	The parse token value
 * @param rest   	String to parse
 * @return 0 and 1 for success and failure
 */
static int
parse_dev_param(build_image_context *context, parse_token token, char *rest)
{
	u_int32_t i;
	u_int32_t value;
	field_item *field;
	u_int32_t index;
	parse_subfield_item *device_item = NULL;

	assert(context != NULL);
	assert(rest != NULL);

	/* Parse the index. */
	rest = parse_u32(rest, &index);
	if (rest == NULL)
		return 1;

	/* Parse the closing bracket. */
	if (*rest != ']')
		return 1;
	rest++;

	/* Parse the following '.' */
	if (*rest != '.')
		return 1;
	rest++;

	/* Parse the device name. */
	for (i = 0; g_soc_config->device_type_table[i].prefix != NULL; i++) {
		if (!strncmp(g_soc_config->device_type_table[i].prefix,
			rest, strlen(g_soc_config->device_type_table[i].prefix))) {

			device_item = &(g_soc_config->device_type_table[i]);
			rest = rest + strlen(g_soc_config->device_type_table[i].prefix);

			/* Parse the field name. */
			rest = parse_field_name(rest,
					g_soc_config->device_type_table[i].field_table,
				&field);
			if (rest == NULL)
				return 1;

			/* Parse the equals sign.*/
			if (*rest != '=')
				return 1;
			rest++;

			/* Parse the value based on the field table. */
			rest = parse_field_value(context, rest, field, &value);
			if (rest == NULL)
				return 1;
			return device_item->process(context,
						index, field->token, value);
		}
	}
	return 1;
}

/*
 * Parse the given string and find sdram parameter and value in config
 * file. If match, call the corresponding function set the sdram parameter.
 *
 * @param context	The main context pointer
 * @param token  	The parse token value
 * @param rest   	String to parse
 * @return 0 and 1 for success and failure
 */
static int
parse_sdram_param(build_image_context *context, parse_token token, char *rest)
{
	u_int32_t value;
	field_item *field;
	u_int32_t index;

	assert(context != NULL);
	assert(rest != NULL);

	/* Parse the index. */
	rest = parse_u32(rest, &index);
	if (rest == NULL)
		return 1;

	/* Parse the closing bracket. */
	if (*rest != ']')
		return 1;
	rest++;

	/* Parse the following '.' */
	if (*rest != '.')
		return 1;
	rest++;

	/* Parse the field name. */
	rest = parse_field_name(rest, g_soc_config->sdram_field_table, &field);

	if (rest == NULL)
		return 1;

	/* Parse the equals sign.*/
	if (*rest != '=')
		return 1;
	rest++;

	/* Parse the value based on the field table. */
	rest = parse_field_value(context, rest, field, &value);
	if (rest == NULL)
		return 1;

	/* Store the result. */
	return g_soc_config->set_sdram_param(context,
						index,
						field->token,
						value);
}

/*
 * Compare the given string with item listed in table.
 * Execute the proper process function if match.
 *
 * @param context	The main context pointer
 * @param str    	String to parse
 * @param simple_parse	Simple parse flag
 * @return 0 and 1 for success and failure
 */
static int
process_statement(build_image_context *context,
			char *str,
			u_int8_t simple_parse)
{
	int i;
	char *rest;
	parse_item *cfg_parse_item;

	if (simple_parse == 0)
		cfg_parse_item = s_top_level_items;
	else
		cfg_parse_item = parse_simple_items;

	for (i = 0; cfg_parse_item[i].prefix != NULL; i++) {
		if (!strncmp(cfg_parse_item[i].prefix, str,
			strlen(cfg_parse_item[i].prefix))) {
			rest = str + strlen(cfg_parse_item[i].prefix);

			return cfg_parse_item[i].process(context,
						cfg_parse_item[i].token,
						rest);
		}
	}

	/* If this point was reached, there was a processing error. */
	return 1;
}

/*
 * The main function parse the config file.
 *
 * @param context     	The main context pointer
 * @param simple_parse	Simple parse flag
 */
void process_config_file(build_image_context *context, u_int8_t simple_parse)
{
	char buffer[MAX_BUFFER];
	int  space = 0;
	int current;
	u_int8_t c_eol_comment_start = 0; /* True after first slash */
	u_int8_t comment = 0;
	u_int8_t string = 0;
	u_int8_t equal_encounter = 0;

	assert(context != NULL);
	assert(context->config_file != NULL);

	while ((current = fgetc(context->config_file)) != EOF) {
		if (space >= (MAX_BUFFER-1)) {
			/* if we exceeded the max buffer size, it is likely
			 due to a missing semi-colon at the end of a line */
			printf("Config file parsing error!");
			exit(1);
		}

		/* Handle failure to complete "//" comment token.
		 Insert the '/' into the busffer and proceed with
		 processing the current character. */
		if (c_eol_comment_start && current != '/') {
			c_eol_comment_start = 0;
			buffer[space++] = '/';
		}

		switch (current) {
		case '\"': /* " indicates start or end of a string */
			if (!comment) {
				string ^= 1;
				buffer[space++] = current;
			}
			break;
		case ';':
			if (!string && !comment) {
				buffer[space++] = '\0';

				if (process_statement(context,
							buffer,
							simple_parse))
					goto error;
				space = 0;
				equal_encounter = 0;
			} else if (string)
				buffer[space++] = current;
			break;

		case '/':
			if (!string && !comment) {
				if (c_eol_comment_start) {
					/* EOL comment started. */
					comment = 1;
					c_eol_comment_start = 0;
				} else {
					/* Potential start of eol comment. */
					c_eol_comment_start = 1;
				}
			} else if (!comment)
				buffer[space++] = current;
			break;

		/* ignore whitespace.  uses fallthrough */
		case '\n':
		case '\r': /* carriage returns end comments */
			string  = 0;
			comment = 0;
			c_eol_comment_start = 0;
		case ' ':
		case '\t':
			if (string)
				buffer[space++] = current;
			break;

		case '#':
			if (!string)
				comment = 1;
			else
				buffer[space++] = current;
			break;

		default:
			if (!comment) {
				buffer[space++] = current;
				if (current == '=') {
					if (!equal_encounter)
						equal_encounter = 1;
					else
						goto error;
				}
			}
			break;
		}
	}

	return;

 error:
	printf("Error parsing: %s\n", buffer);
	exit(1);
}
