// SPDX-License-Identifier: BSD-2-Clause-Patent

#ifndef SHIM_STR_H
#define SHIM_STR_H

#if __GNUC__ > 6
#ifdef SHIM_UNIT_TEST
#pragma GCC diagnostic error "-Wnonnull-compare"
#else
#pragma GCC diagnostic ignored "-Wnonnull-compare"
#endif
#endif

static inline UNUSED RETURNS_NONNULL NONNULL(1)
char *
strnchrnul(const char *s, size_t max, int c)
{
	unsigned int i;

	if (!s || !max)
		return (char *)s;

	for (i = 0; i < max && s[i] != '\0' && s[i] != c; i++)
		;

	if (i == max)
		i--;

	return (char *)&s[i];
}

/**
 * strntoken: tokenize a string, with a limit
 * str: your string (will be modified)
 * max: maximum number of bytes to ever touch
 * delims: string of one character delimeters, any of which will tokenize
 * *token: the token we're passing back (must be a pointer to NULL initially)
 * state: a pointer to one char of state for between calls
 *
 * Ensure that both token and state are preserved across calls.  Do:
 *   char state = 0;
 *   char *token = NULL;
 *   for (...) {
 *     valid = strntoken(...)
 * not:
 *   char state = 0;
 *   for (...) {
 *     char *token = NULL;
 *     valid = strntoken(...)
 *
 * - it will not test bytes beyond str[max-1]
 * - it will not set *token to an address beyond &str[max-1]
 * - it will set *token to &str[max-1] without testing &str[max-2] for
 *   &str[max-1] == str
 * - sequences of multiple delimeters will result in empty (pointer to '\0')
 *   tokens.
 * - it expects you to update str and max on successive calls.
 *
 * return:
 * true means it hasn't tested str[max-1] yet and token is valid
 * false means it got to a NUL or str[max-1] and token is invalid
 */
static inline UNUSED NONNULL(1, 3, 4) int
strntoken(char *str, size_t max, const char *delims, char **token, char *state)
{
	char *tokend;
	const char *delim;
	int isdelim = 0;
	int state_is_delim = 0;

	if (!str || !max || !delims || !token || !state)
		return 0;

	tokend = &str[max-1];
	if (!str || max == 0 || !delims || !token)
		return 0;

	/*
	 * the very special case of "" with max=1, where we have no prior
	 * state to let us know this is the same as right after a delim
	 */
	if (*token == NULL && max == 1 && *str == '\0') {
		state_is_delim = 1;
	}

	for (delim = delims; *delim; delim++) {
		char *tmp = NULL;
		if (*token && *delim == *state)
			state_is_delim = 1;
		tmp = strnchrnul(str, max, *delim);
		if (tmp < tokend)
			tokend = tmp;
		if (*tokend == *delim)
			isdelim = 1;
	}
	*token = str;
	if (isdelim) {
		*state = *tokend;
		*tokend = '\0';
		return 1;
	}
	return state_is_delim;
}

#define UTF8_BOM { 0xef, 0xbb, 0xbf }
#define UTF8_BOM_SIZE 3

static inline UNUSED NONNULL(1) BOOLEAN
is_utf8_bom(CHAR8 *buf, size_t bufsize)
{
	unsigned char bom[] = UTF8_BOM;

	return CompareMem(buf, bom, MIN(UTF8_BOM_SIZE, bufsize)) == 0;
}

/**
 * parse CSV data from data to end.
 * *data	points to the first byte of the data
 * end		points to a NUL byte at the end of the data
 * n_columns	number of columns per entry
 * list		the list head we're adding to
 *
 * On success, list will be populated with individually allocate a list of
 * struct csv_list objects, with one column per entry of the "columns" array,
 * filled left to right with up to n_columns elements, or NULL when a csv line
 * does not have enough elements.
 *
 * Note that the data will be modified; all comma, linefeed, and newline
 * characters will be set to '\000'.  Additionally, consecutive linefeed and
 * newline characters will not result in rows in the results.
 *
 * On failure, list will be empty and all entries on it will have been freed,
 * using free_csv_list(), whether they were there before calling
 * parse_csv_data or not.
 */

struct csv_row {
	list_t list;		/* this is a linked list */
	size_t n_columns;	/* this is how many columns are actually populated */
	char *columns[0];	/* these are pointers to columns */
};

EFI_STATUS parse_csv_data(char *data, char *end, size_t n_columns,
                          list_t *list);
void free_csv_list(list_t *list);

#ifdef SHIM_UNIT_TEST
void NONNULL(1, 3, 4)
parse_csv_line(char * line, size_t max, size_t *n_columns, const char *columns[]);
#endif

#endif /* SHIM_STR_H */
