// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
 *
 * (C) COPYRIGHT 2017-2023 ARM Limited. All rights reserved.
 *
 * This program is free software and is provided to you under the terms of the
 * GNU General Public License version 2 as published by the Free Software
 * Foundation, and any use by you of this program is subject to the terms
 * of such GNU license.
 *
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, you can access it online at
 * http://www.gnu.org/licenses/gpl-2.0.html.
 *
 */

/* Kernel UTF test helpers that mirror those for kutf-userside */
#include <kutf/kutf_helpers_user.h>
#include <kutf/kutf_helpers.h>
#include <kutf/kutf_utils.h>

#include <linux/err.h>
#include <linux/slab.h>
#include <linux/export.h>

static const char *const valtype_names[] = {
	"INVALID",
	"U64",
	"STR",
};

static const char *get_val_type_name(enum kutf_helper_valtype valtype)
{
	/* enums can be signed or unsigned (implementation dependant), so
	 * enforce it to prevent:
	 * a) "<0 comparison on unsigned type" warning - if we did both upper
	 *    and lower bound check
	 * b) incorrect range checking if it was a signed type - if we did
	 *    upper bound check only
	 */
	unsigned int type_idx = (unsigned int)valtype;

	if (type_idx >= (unsigned int)KUTF_HELPER_VALTYPE_COUNT)
		type_idx = (unsigned int)KUTF_HELPER_VALTYPE_INVALID;

	return valtype_names[type_idx];
}

/* Check up to str_len chars of val_str to see if it's a valid value name:
 *
 * - Has between 1 and KUTF_HELPER_MAX_VAL_NAME_LEN characters before the \0 terminator
 * - And, each char is in the character set [A-Z0-9_]
 */
static int validate_val_name(const char *val_str, size_t str_len)
{
	int i = 0;

	for (i = 0; str_len && i <= KUTF_HELPER_MAX_VAL_NAME_LEN && val_str[i] != '\0';
	     ++i, --str_len) {
		char val_chr = val_str[i];

		if (val_chr >= 'A' && val_chr <= 'Z')
			continue;
		if (val_chr >= '0' && val_chr <= '9')
			continue;
		if (val_chr == '_')
			continue;

		/* Character not in the set [A-Z0-9_] - report error */
		return 1;
	}

	/* Names of 0 length are not valid */
	if (i == 0)
		return 1;
	/* Length greater than KUTF_HELPER_MAX_VAL_NAME_LEN not allowed */
	if (i > KUTF_HELPER_MAX_VAL_NAME_LEN ||
	    (i == KUTF_HELPER_MAX_VAL_NAME_LEN && val_str[i] != '\0'))
		return 1;

	return 0;
}

/* Find the length of the valid part of the string when it will be in quotes
 * e.g. "str"
 *
 * That is, before any '\\', '\n' or '"' characters. This is so we don't have
 * to escape the string
 */
static size_t find_quoted_string_valid_len(const char *str)
{
	char *ptr;
	const char *check_chars = "\\\n\"";

	ptr = strpbrk(str, check_chars);
	if (ptr)
		return (size_t)(ptr - str);

	return strlen(str);
}

static int kutf_helper_userdata_enqueue(struct kutf_context *context, const char *str)
{
	char *str_copy;
	size_t len;
	int err;

	len = strlen(str) + 1;

	str_copy = kutf_mempool_alloc(&context->fixture_pool, len);
	if (!str_copy)
		return -ENOMEM;

	strscpy(str_copy, str, len);

	err = kutf_add_result(context, KUTF_RESULT_USERDATA, str_copy);

	return err;
}

#define MAX_U64_HEX_LEN 16
/* (Name size) + ("=0x" size) + (64-bit hex value size) + (terminator) */
#define NAMED_U64_VAL_BUF_SZ (KUTF_HELPER_MAX_VAL_NAME_LEN + 3 + MAX_U64_HEX_LEN + 1)

int kutf_helper_send_named_u64(struct kutf_context *context, const char *val_name, u64 val)
{
	int ret = 1;
	char msgbuf[NAMED_U64_VAL_BUF_SZ];
	const char *errmsg = NULL;

	if (validate_val_name(val_name, KUTF_HELPER_MAX_VAL_NAME_LEN + 1)) {
		errmsg = kutf_dsprintf(&context->fixture_pool,
				       "Failed to send u64 value named '%s': Invalid value name",
				       val_name);
		goto out_err;
	}

	ret = snprintf(msgbuf, NAMED_U64_VAL_BUF_SZ, "%s=0x%llx", val_name, val);
	if (ret >= NAMED_U64_VAL_BUF_SZ || ret < 0) {
		errmsg = kutf_dsprintf(
			&context->fixture_pool,
			"Failed to send u64 value named '%s': snprintf() problem buffer size==%d ret=%d",
			val_name, NAMED_U64_VAL_BUF_SZ, ret);
		goto out_err;
	}

	ret = kutf_helper_userdata_enqueue(context, msgbuf);
	if (ret) {
		errmsg = kutf_dsprintf(&context->fixture_pool,
				       "Failed to send u64 value named '%s': send returned %d",
				       val_name, ret);
		goto out_err;
	}

	return ret;
out_err:
	kutf_test_fail(context, errmsg);
	return ret;
}
EXPORT_SYMBOL(kutf_helper_send_named_u64);

#define NAMED_VALUE_SEP "="
#define NAMED_STR_START_DELIM NAMED_VALUE_SEP "\""
#define NAMED_STR_END_DELIM "\""

int kutf_helper_max_str_len_for_kern(const char *val_name, int kern_buf_sz)
{
	const int val_name_len = strlen(val_name);
	const int start_delim_len = strlen(NAMED_STR_START_DELIM);
	const int end_delim_len = strlen(NAMED_STR_END_DELIM);
	int max_msg_len = kern_buf_sz;
	int max_str_len;

	max_str_len = max_msg_len - val_name_len - start_delim_len - end_delim_len;

	return max_str_len;
}
EXPORT_SYMBOL(kutf_helper_max_str_len_for_kern);

int kutf_helper_send_named_str(struct kutf_context *context, const char *val_name,
			       const char *val_str)
{
	size_t val_str_len;
	size_t str_buf_sz;
	char *str_buf = NULL;
	int ret = 1;
	char *copy_ptr;
	size_t val_name_len;
	size_t start_delim_len = strlen(NAMED_STR_START_DELIM);
	size_t end_delim_len = strlen(NAMED_STR_END_DELIM);
	const char *errmsg = NULL;

	if (validate_val_name(val_name, KUTF_HELPER_MAX_VAL_NAME_LEN + 1)) {
		errmsg = kutf_dsprintf(&context->fixture_pool,
				       "Failed to send u64 value named '%s': Invalid value name",
				       val_name);
		goto out_err;
	}
	val_name_len = strlen(val_name);

	val_str_len = find_quoted_string_valid_len(val_str);

	/* (name length) + ("=\"" length) + (val_str len) + ("\"" length) + terminator */
	str_buf_sz = val_name_len + start_delim_len + val_str_len + end_delim_len + 1;

	/* Using kmalloc() here instead of mempool since we know we need to free
	 * before we return
	 */
	str_buf = kmalloc(str_buf_sz, GFP_KERNEL);
	if (!str_buf) {
		errmsg = kutf_dsprintf(
			&context->fixture_pool,
			"Failed to send str value named '%s': kmalloc failed, str_buf_sz=%zu",
			val_name, str_buf_sz);
		goto out_err;
	}
	copy_ptr = str_buf;

	/* Manually copy each string component instead of snprintf because
	 * val_str may need to end early, and less error path handling
	 */

	/* name */
	memcpy(copy_ptr, val_name, val_name_len);
	copy_ptr += val_name_len;

	/* str start delimiter */
	memcpy(copy_ptr, NAMED_STR_START_DELIM, start_delim_len);
	copy_ptr += start_delim_len;

	/* str value */
	memcpy(copy_ptr, val_str, val_str_len);
	copy_ptr += val_str_len;

	/* str end delimiter */
	memcpy(copy_ptr, NAMED_STR_END_DELIM, end_delim_len);
	copy_ptr += end_delim_len;

	/* Terminator */
	*copy_ptr = '\0';

	ret = kutf_helper_userdata_enqueue(context, str_buf);

	if (ret) {
		errmsg = kutf_dsprintf(&context->fixture_pool,
				       "Failed to send str value named '%s': send returned %d",
				       val_name, ret);
		goto out_err;
	}

	kfree(str_buf);
	return ret;

out_err:
	kutf_test_fail(context, errmsg);
	kfree(str_buf);
	return ret;
}
EXPORT_SYMBOL(kutf_helper_send_named_str);

int kutf_helper_receive_named_val(struct kutf_context *context,
				  struct kutf_helper_named_val *named_val)
{
	size_t recv_sz;
	char *recv_str;
	char *search_ptr;
	char *name_str = NULL;
	size_t name_len;
	size_t strval_len;
	enum kutf_helper_valtype type = KUTF_HELPER_VALTYPE_INVALID;
	char *strval = NULL;
	u64 u64val = 0;
	int err = KUTF_HELPER_ERR_INVALID_VALUE;

	recv_str = kutf_helper_input_dequeue(context, &recv_sz);
	if (!recv_str)
		return -EBUSY;
	else if (IS_ERR(recv_str))
		return PTR_ERR(recv_str);

	/* Find the '=', grab the name and validate it */
	search_ptr = strnchr(recv_str, recv_sz, NAMED_VALUE_SEP[0]);
	if (search_ptr) {
		name_len = (size_t)(search_ptr - recv_str);
		if (!validate_val_name(recv_str, name_len)) {
			/* no need to reallocate - just modify string in place */
			name_str = recv_str;
			name_str[name_len] = '\0';

			/* Move until after the '=' */
			recv_str += (name_len + 1);
			recv_sz -= (name_len + 1);
		}
	}
	if (!name_str) {
		pr_err("Invalid name part for received string '%s'\n", recv_str);
		return KUTF_HELPER_ERR_INVALID_NAME;
	}

	/* detect value type */
	if (*recv_str == NAMED_STR_START_DELIM[1]) {
		/* string delimiter start*/
		++recv_str;
		--recv_sz;

		/* Find end of string */
		search_ptr = strnchr(recv_str, recv_sz, NAMED_STR_END_DELIM[0]);
		if (search_ptr) {
			strval_len = (size_t)(search_ptr - recv_str);
			/* Validate the string to ensure it contains no quotes */
			if (strval_len == find_quoted_string_valid_len(recv_str)) {
				/* no need to reallocate - just modify string in place */
				strval = recv_str;
				strval[strval_len] = '\0';

				/* Move until after the end delimiter */
				recv_str += (strval_len + 1);
				recv_sz -= (strval_len + 1);
				type = KUTF_HELPER_VALTYPE_STR;
			} else {
				pr_err("String value contains invalid characters in rest of received string '%s'\n",
				       recv_str);
				err = KUTF_HELPER_ERR_CHARS_AFTER_VAL;
			}
		} else {
			pr_err("End of string delimiter not found in rest of received string '%s'\n",
			       recv_str);
			err = KUTF_HELPER_ERR_NO_END_DELIMITER;
		}
	} else {
		/* possibly a number value - strtoull will parse it */
		err = kstrtoull(recv_str, 0, &u64val);
		/* unlike userspace can't get an end ptr, but if kstrtoull()
		 * reads characters after the number it'll report -EINVAL
		 */
		if (!err) {
			size_t len_remain = strnlen(recv_str, recv_sz);

			type = KUTF_HELPER_VALTYPE_U64;
			recv_str += len_remain;
			recv_sz -= len_remain;
		} else {
			/* special case: not a number, report as such */
			pr_err("Rest of received string was not a numeric value or quoted string value: '%s'\n",
			       recv_str);
		}
	}

	if (type == KUTF_HELPER_VALTYPE_INVALID)
		return err;

	/* Any remaining characters - error */
	if (strnlen(recv_str, recv_sz) != 0) {
		pr_err("Characters remain after value of type %s: '%s'\n", get_val_type_name(type),
		       recv_str);
		return KUTF_HELPER_ERR_CHARS_AFTER_VAL;
	}

	/* Success - write into the output structure */
	switch (type) {
	case KUTF_HELPER_VALTYPE_U64:
		named_val->u.val_u64 = u64val;
		break;
	case KUTF_HELPER_VALTYPE_STR:
		named_val->u.val_str = strval;
		break;
	default:
		pr_err("Unreachable, fix %s\n", __func__);
		/* Coding error, report as though 'run' file failed */
		return -EINVAL;
	}

	named_val->val_name = name_str;
	named_val->type = type;

	return KUTF_HELPER_ERR_NONE;
}
EXPORT_SYMBOL(kutf_helper_receive_named_val);

#define DUMMY_MSG "<placeholder due to test fail>"
int kutf_helper_receive_check_val(struct kutf_helper_named_val *named_val,
				  struct kutf_context *context, const char *expect_val_name,
				  enum kutf_helper_valtype expect_val_type)
{
	int err;

	err = kutf_helper_receive_named_val(context, named_val);
	if (err < 0) {
		const char *msg = kutf_dsprintf(&context->fixture_pool,
						"Failed to receive value named '%s'",
						expect_val_name);
		kutf_test_fail(context, msg);
		return err;
	} else if (err > 0) {
		const char *msg = kutf_dsprintf(
			&context->fixture_pool,
			"Named-value parse error when expecting value named '%s'", expect_val_name);
		kutf_test_fail(context, msg);
		goto out_fail_and_fixup;
	}

	if (named_val->val_name != NULL && strcmp(named_val->val_name, expect_val_name) != 0) {
		const char *msg =
			kutf_dsprintf(&context->fixture_pool,
				      "Expecting to receive value named '%s' but got '%s'",
				      expect_val_name, named_val->val_name);
		kutf_test_fail(context, msg);
		goto out_fail_and_fixup;
	}

	if (named_val->type != expect_val_type) {
		const char *msg = kutf_dsprintf(
			&context->fixture_pool,
			"Expecting value named '%s' to be of type %s but got %s", expect_val_name,
			get_val_type_name(expect_val_type), get_val_type_name(named_val->type));
		kutf_test_fail(context, msg);
		goto out_fail_and_fixup;
	}

	return err;

out_fail_and_fixup:
	/* Produce a valid but incorrect value */
	switch (expect_val_type) {
	case KUTF_HELPER_VALTYPE_U64:
		named_val->u.val_u64 = 0ull;
		break;
	case KUTF_HELPER_VALTYPE_STR: {
		char *str = kutf_mempool_alloc(&context->fixture_pool, sizeof(DUMMY_MSG));

		if (!str)
			return -1;

		strcpy(str, DUMMY_MSG);
		named_val->u.val_str = str;
		break;
	}
	default:
		break;
	}

	/* Indicate that this is invalid */
	named_val->type = KUTF_HELPER_VALTYPE_INVALID;

	/* But at least allow the caller to continue in the test with failures */
	return 0;
}
EXPORT_SYMBOL(kutf_helper_receive_check_val);

void kutf_helper_output_named_val(struct kutf_helper_named_val *named_val)
{
	switch (named_val->type) {
	case KUTF_HELPER_VALTYPE_U64:
		pr_warn("%s=0x%llx\n", named_val->val_name, named_val->u.val_u64);
		break;
	case KUTF_HELPER_VALTYPE_STR:
		pr_warn("%s=\"%s\"\n", named_val->val_name, named_val->u.val_str);
		break;
	case KUTF_HELPER_VALTYPE_INVALID:
		pr_warn("%s is invalid\n", named_val->val_name);
		break;
	default:
		pr_warn("%s has unknown type %d\n", named_val->val_name, named_val->type);
		break;
	}
}
EXPORT_SYMBOL(kutf_helper_output_named_val);
