~rcr/rirc

3e7e5419b27f2b7ec6408fb2d3ef73eceb4d74a2 — Richard Robbins a month ago e3b5e0c
cleanup and simplify mode handling
M src/components/mode.c => src/components/mode.c +111 -348
@@ 5,70 5,54 @@
#include <ctype.h>
#include <string.h>

#define MODE_ISLOWER(X) ((X) >= 'a' && (X) <= 'z')
#define MODE_ISUPPER(X) ((X) >= 'A' && (X) <= 'Z')

/* Set bit Y of X to the value of Z: [0, 1] */
#define MODE_SET(X, Y, Z) ((X) ^= (-(Z) ^ (X)) & (Y))

enum mode_chanmode_prefix
{
	MODE_CHANMODE_PREFIX_SECRET  = '@', /* chanmode 's' */
	MODE_CHANMODE_PREFIX_PRIVATE = '*', /* chanmode 'p' */
	MODE_CHANMODE_PREFIX_OTHER   = '=',
};
#define MODE_SET_BIT(X, Y, Z) ((X) ^= (-(Z) ^ (X)) & (Y))

static inline int mode_isset(const struct mode*, int);
static inline uint32_t flag_bit(int);
static int mode_isset(const struct mode*, int);
static void mode_set(struct mode*, int, int);
static uint32_t mode_bit(uint8_t);

static enum mode_err mode_cfg_chanmodes(struct mode_cfg*, const char*);
static enum mode_err mode_cfg_usermodes(struct mode_cfg*, const char*);
static enum mode_err mode_cfg_subtypes(struct mode_cfg*, const char*);
static enum mode_err mode_cfg_prefix(struct mode_cfg*, const char*);
static enum mode_err mode_cfg_modes(struct mode_cfg*, const char*);

/* TODO: check validity of set_t on all mode settings */
/* TODO: static inline void mode_bit_set(struct mode*, uint32_t); */
/* TODO: static inline void mode_bit_isset(struct mode*, uint32_t); */
/* TODO: aggregate errors with logging callback */
/* TODO: safe channels ('!' prefix) (see RFC2811) */

static inline int
mode_isset(const struct mode *m, int flag)
static uint32_t
mode_bit(uint8_t c)
{
	/* Test if mode flag is set, assumes valid flag */
	static const uint32_t mode_bits[] = {
		['a'] = (1U << 0), ['j'] = (1U << 9),  ['s'] = (1U << 18),
		['b'] = (1U << 1), ['k'] = (1U << 10), ['t'] = (1U << 19),
		['c'] = (1U << 2), ['l'] = (1U << 11), ['u'] = (1U << 20),
		['d'] = (1U << 3), ['m'] = (1U << 12), ['v'] = (1U << 21),
		['e'] = (1U << 4), ['n'] = (1U << 13), ['w'] = (1U << 22),
		['f'] = (1U << 5), ['o'] = (1U << 14), ['x'] = (1U << 23),
		['g'] = (1U << 6), ['p'] = (1U << 15), ['y'] = (1U << 24),
		['h'] = (1U << 7), ['q'] = (1U << 16), ['z'] = (1U << 25),
		['i'] = (1U << 8), ['r'] = (1U << 17), [UINT8_MAX] = 0
	};

	if (MODE_ISLOWER(flag) && (m->lower & flag_bit(flag)))
		return 1;
	return mode_bits[tolower(c)];
}

	if (MODE_ISUPPER(flag) && (m->upper & flag_bit(flag)))
		return 1;
static void
mode_set(struct mode *m, int flag, int set)
{
	if (islower(flag))
		MODE_SET_BIT(m->lower, mode_bit(flag), !!set);

	return 0;
	if (isupper(flag))
		MODE_SET_BIT(m->upper, mode_bit(flag), !!set);
}

static inline uint32_t
flag_bit(int c)
static int
mode_isset(const struct mode *m, int flag)
{
	/* Map input character to [az-AZ] bit flag */

	static const uint32_t flag_bits[] = {
		1U << 0,  /* a */ 1U << 1,  /* b */ 1U << 2,  /* c */
		1U << 3,  /* d */ 1U << 4,  /* e */ 1U << 5,  /* f */
		1U << 6,  /* g */ 1U << 7,  /* h */ 1U << 8,  /* i */
		1U << 9,  /* j */ 1U << 10, /* k */ 1U << 11, /* l */
		1U << 12, /* m */ 1U << 13, /* n */ 1U << 14, /* o */
		1U << 15, /* p */ 1U << 16, /* q */ 1U << 17, /* r */
		1U << 18, /* s */ 1U << 19, /* t */ 1U << 20, /* u */
		1U << 21, /* v */ 1U << 22, /* w */ 1U << 23, /* x */
		1U << 24, /* y */ 1U << 25, /* z */
	};

	if (MODE_ISLOWER(c))
		return flag_bits[c - 'a'];
	if (islower(flag) && (m->lower & mode_bit(flag)))
		return 1;

	if (MODE_ISUPPER(c))
		return flag_bits[c - 'A'];
	if (isupper(flag) && (m->upper & mode_bit(flag)))
		return 1;

	return 0;
}


@@ 99,10 83,9 @@ mode_cfg(struct mode_cfg *cfg, const char *cfg_str, enum mode_cfg_type cfg_type)
	 *
	 *   b - set/remove ban mask to keep users out;
	 *   e - set/remove an exception mask to override a ban mask;
	 *   I - set/remove an invitation mask to automatically override the
	 *       invite-only flag;
	 *   I - set/remove an invitation mask to automatically override the invite-only flag;
	 *
	 * Usermodes (RFC2811, section 3.1.5)
	 * Usermodes (RFC2812, section 3.1.5)
	 *
	 *   a - user is flagged as away;
	 *   i - marks a users as invisible;


@@ 111,33 94,17 @@ mode_cfg(struct mode_cfg *cfg, const char *cfg_str, enum mode_cfg_type cfg_type)
	 *   o - operator flag;
	 *   O - local operator flag;
	 *   s - marks a user for receipt of server notices.
	 *
	 * MODES (RFC2811, section 3.2.3)
	 *
	 *   "Note that there is a maximum limit of three (3) changes per command
	 *    for modes that take a parameter."
	 *
	 * Note: PREFIX, MODES and CHANMODES are ubiquitous additions to the IRC
	 *       protocol given by numeric 005 (RPL_ISUPPORT). As such,
	 *       they've been interpreted here in terms of A,B,C,D subcategories
	 *       for the sake of default settings. Numeric 319 (RPL_WHOISCHANNELS)
	 *       states chanmode user prefixes map o,v to @,+ respectively.
	 */

	switch (cfg_type) {

		case MODE_CFG_DEFAULTS:
			*cfg = (struct mode_cfg)
			{
				.PREFIX = {
					.F = "ov",
					.T = "@+"
				},
				.MODES = 3
			};
			memset(cfg, 0, sizeof(*cfg));
			(void) snprintf(cfg->PREFIX.F, sizeof(cfg->PREFIX.F), "ov");
			(void) snprintf(cfg->PREFIX.T, sizeof(cfg->PREFIX.T), "@+");
			mode_cfg_chanmodes(cfg, "OovaimnqpsrtklbeI");
			mode_cfg_usermodes(cfg, "aiwroOs");
			mode_cfg_subtypes(cfg, "beI,k,l,aimnqpsrtO");
			mode_cfg_subtypes(cfg, "IObe,k,l,aimnqpsrt");
			break;

		case MODE_CFG_CHANMODES:


@@ 152,9 119,6 @@ mode_cfg(struct mode_cfg *cfg, const char *cfg_str, enum mode_cfg_type cfg_type)
		case MODE_CFG_PREFIX:
			return mode_cfg_prefix(cfg, cfg_str);

		case MODE_CFG_MODES:
			return mode_cfg_modes(cfg, cfg_str);

		default:
			fatal("mode configuration type unknown: %d", cfg_type);
	}


@@ 163,127 127,33 @@ mode_cfg(struct mode_cfg *cfg, const char *cfg_str, enum mode_cfg_type cfg_type)
}

enum mode_err
mode_chanmode_set(struct mode *m, const struct mode_cfg *cfg, int flag, enum mode_set set)
mode_chanmode_set(struct mode *m, const struct mode_cfg *cfg, int flag, int set)
{
	/* Set/unset chanmode flags
	 *
	 * Only CHANMODE subtypes B,C,D set/unset flags for the channel
	 *
	 * ---
	 *
	 * RFC2812, section 5.1, numeric reply 353 (RPL_NAMREPLY)
	 *
	 * "@" is used for secret channels,   ('s' flag)
	 * "*" for private channels, and      ('p' flag)
	 * "=" for others (public channels).
	 *
	 * RFC2811, section 4.2.6 Private and Secret Channels
	 *
	 * The channel flag 'p' is used to mark a channel "private" and the
	 * channel flag 's' to mark a channel "secret".  Both properties are
	 * similar and conceal the existence of the channel from other users.
	 *
	 * This means that there is no way of getting this channel's name from
	 * the server without being a member.  In other words, these channels
	 * MUST be omitted from replies to queries like the WHOIS command.
	 *
	 * When a channel is "secret", in addition to the restriction above, the
	 * server will act as if the channel does not exist for queries like the
	 * TOPIC, LIST, NAMES commands.  Note that there is one exception to
	 * this rule: servers will correctly reply to the MODE command.
	 * Finally, secret channels are not accounted for in the reply to the
	 * LUSERS command (See "Internet Relay Chat: Client Protocol" [IRC-
	 * CLIENT]) when the <mask> parameter is specified.
	 *
	 * The channel flags 'p' and 's' MUST NOT both be set at the same time.
	 * If a MODE message originating from a server sets the flag 'p' and the
	 * flag 's' is already set for the channel, the change is silently
	 * ignored.  This should only happen during a split healing phase
	 * (mentioned in the "IRC Server Protocol" document [IRC-SERVER]).
	 */

	if (!(set == MODE_SET_ON || set == MODE_SET_OFF))
		return MODE_ERR_INVALID_SET;
	/* Set/unset chanmode flags */

	if (!mode_isset(&(cfg->chanmodes), flag))
		return MODE_ERR_INVALID_FLAG;

	if (set == MODE_SET_ON && mode_isset(m, flag))
		return MODE_ERR_DUPLICATE;

	/* Mode subtypes A don't set a flag */
	if (mode_isset(&(cfg->CHANMODES.A), flag))
		return MODE_ERR_NONE;

	if (flag != 's' && flag != 'p') {

		uint32_t bit = flag_bit(flag);

		if (MODE_ISLOWER(flag))
			MODE_SET(m->lower, bit, set);
		else
			MODE_SET(m->upper, bit, set);

	} else if (flag == 'p') {

		/* Silently ignore */
		if (mode_isset(m, 's'))
			return MODE_ERR_NONE;

		if (set == MODE_SET_OFF) {
			MODE_SET(m->lower, flag_bit('p'), MODE_SET_OFF);

			m->prefix = MODE_CHANMODE_PREFIX_OTHER;
		}

		if (set == MODE_SET_ON) {
			MODE_SET(m->lower, flag_bit('p'), MODE_SET_ON);

			m->prefix = MODE_CHANMODE_PREFIX_PRIVATE;
		}

	} else if (flag == 's') {

		if (set == MODE_SET_OFF) {
			MODE_SET(m->lower, flag_bit('s'), MODE_SET_OFF);
			MODE_SET(m->lower, flag_bit('p'), MODE_SET_OFF);

			m->prefix = MODE_CHANMODE_PREFIX_OTHER;
		}

		if (set == MODE_SET_ON) {
			MODE_SET(m->lower, flag_bit('s'), MODE_SET_ON);
			MODE_SET(m->lower, flag_bit('p'), MODE_SET_OFF);

			m->prefix = MODE_CHANMODE_PREFIX_SECRET;
		}
	}
	mode_set(m, flag, set);

	return MODE_ERR_NONE;
}

enum mode_err
mode_prfxmode_set(struct mode *m, const struct mode_cfg *cfg, int flag, enum mode_set set)
mode_prfxmode_set(struct mode *m, const struct mode_cfg *cfg, int flag, int set)
{
	/* Set/unset prfxmode flags and mode prefix */

	uint32_t bit;

	if (!(set == MODE_SET_ON || set == MODE_SET_OFF))
		return MODE_ERR_INVALID_SET;

	if (!strchr(cfg->PREFIX.F, flag))
		return MODE_ERR_INVALID_FLAG;

	bit = flag_bit(flag);

	if (MODE_ISLOWER(flag))
		MODE_SET(m->lower, bit, set);
	else
		MODE_SET(m->upper, bit, set);
	const char *f = cfg->PREFIX.F;
	const char *t = cfg->PREFIX.T;

	const char *f = cfg->PREFIX.F,
	           *t = cfg->PREFIX.T;
	mode_set(m, flag, set);

	while (*f) {



@@ 300,66 170,14 @@ mode_prfxmode_set(struct mode *m, const struct mode_cfg *cfg, int flag, enum mod
}

enum mode_err
mode_usermode_set(struct mode *m, const struct mode_cfg *cfg, int flag, enum mode_set set)
mode_usermode_set(struct mode *m, const struct mode_cfg *cfg, int flag, int set)
{
	/* Set/unset usermode flags */

	uint32_t bit;

	if (!(set == MODE_SET_ON || set == MODE_SET_OFF))
		return MODE_ERR_INVALID_SET;

	if (!mode_isset(&(cfg->usermodes), flag))
		return MODE_ERR_INVALID_FLAG;

	bit = flag_bit(flag);

	if (MODE_ISLOWER(flag))
		MODE_SET(m->lower, bit, set);
	else
		MODE_SET(m->upper, bit, set);

	return MODE_ERR_NONE;
}

enum mode_err
mode_chanmode_prefix(struct mode *m, const struct mode_cfg *cfg, int flag)
{
	/* Set chanmode flag and prefix give the prefix character, e.g.:
	 *
	 * - '@' sets 's', unsets 'p'
	 * - '*' sets 'p'
	 * - '=' sets neither
	 *
	 * All other prefixes are invalid.
	 * Prefixes may override by precendece, but are silentyly ignored otherwise */

	UNUSED(cfg);

	/* If 's' is set, all other settings are silently ignored */
	if (m->prefix == MODE_CHANMODE_PREFIX_SECRET)
		return MODE_ERR_NONE;

	/* If 'p' is set, only SECRET prefix is accepted */
	if (m->prefix == MODE_CHANMODE_PREFIX_PRIVATE && flag != MODE_CHANMODE_PREFIX_SECRET)
		return MODE_ERR_NONE;

	/* Otherwise, all valid prefixes can be set */
	switch (flag) {
		case MODE_CHANMODE_PREFIX_SECRET:
			MODE_SET(m->lower, flag_bit('p'), MODE_SET_OFF);
			MODE_SET(m->lower, flag_bit('s'), MODE_SET_ON);
			break;
		case MODE_CHANMODE_PREFIX_PRIVATE:
			MODE_SET(m->lower, flag_bit('p'), MODE_SET_ON);
			break;
		case MODE_CHANMODE_PREFIX_OTHER:
			break;
		default:
			return MODE_ERR_INVALID_PREFIX;
	}

	m->prefix = flag;
	mode_set(m, flag, set);

	return MODE_ERR_NONE;
}


@@ 374,10 192,8 @@ mode_prfxmode_prefix(struct mode *m, const struct mode_cfg *cfg, int flag)
	 *   - prfxmode_prefix(cfg, mode, '+')   sets mode flag 'v'
	 */

	uint32_t bit;

	const char *f = cfg->PREFIX.F,
	           *t = cfg->PREFIX.T;
	const char *f = cfg->PREFIX.F;
	const char *t = cfg->PREFIX.T;

	while (*t && *t != flag) {
		f++;


@@ 387,12 203,7 @@ mode_prfxmode_prefix(struct mode *m, const struct mode_cfg *cfg, int flag)
	if (*t == 0)
		return MODE_ERR_INVALID_PREFIX;

	bit = flag_bit(*f);

	if (MODE_ISLOWER(*f))
		MODE_SET(m->lower, bit, MODE_SET_ON);
	else
		MODE_SET(m->upper, bit, MODE_SET_ON);
	mode_set(m, *f, 1);

	f = cfg->PREFIX.F,
	t = cfg->PREFIX.T;


@@ 412,11 223,10 @@ mode_str(const struct mode *m, struct mode_str *m_str)
{
	/* Write the mode bits to a mode string */

	char c;
	char *str = m_str->str;

	uint32_t lower = m->lower,
	         upper = m->upper;
	uint32_t lower = m->lower;
	uint32_t upper = m->upper;

	switch (m_str->type) {
		case MODE_STR_CHANMODE:


@@ 429,11 239,11 @@ mode_str(const struct mode *m, struct mode_str *m_str)
			fatal("mode_str type unknown");
	}

	for (c = 'a'; c <= 'z' && lower; c++, lower >>= 1)
	for (char c = 'a'; c <= 'z' && lower; c++, lower >>= 1)
		if (lower & 1)
			*str++ = c;

	for (c = 'A'; c <= 'Z' && upper; c++, upper >>= 1)
	for (char c = 'A'; c <= 'Z' && upper; c++, upper >>= 1)
		if (upper & 1)
			*str++ = c;



@@ 471,18 281,13 @@ mode_cfg_chanmodes(struct mode_cfg *cfg, const char *str)

	while ((c = *str++)) {

		uint32_t bit;

		if ((bit = flag_bit(c)) == 0)
			continue; /* TODO: aggregate warnings, invalid flag */
		if (!mode_bit(c))
			continue;

		if (mode_isset(chanmodes, c))
			continue; /* TODO: aggregate warnings, duplicate flag */
			continue;

		if (MODE_ISLOWER(c))
			MODE_SET(chanmodes->lower, bit, MODE_SET_ON);
		else
			MODE_SET(chanmodes->upper, bit, MODE_SET_ON);
		mode_set(chanmodes, c, 1);
	}

	return MODE_ERR_NONE;


@@ 501,18 306,13 @@ mode_cfg_usermodes(struct mode_cfg *cfg, const char *str)

	while ((c = *str++)) {

		uint32_t bit;

		if ((bit = flag_bit(c)) == 0)
			continue; /* TODO: aggregate warnings, invalid flag */
		if (!mode_bit(c))
			continue;

		if (mode_isset(usermodes, c))
			continue; /* TODO: aggregate warnings, duplicate flag */
			continue;

		if (MODE_ISLOWER(c))
			MODE_SET(usermodes->lower, bit, MODE_SET_ON);
		else
			MODE_SET(usermodes->upper, bit, MODE_SET_ON);
		mode_set(usermodes, c, 1);
	}

	return MODE_ERR_NONE;


@@ 524,10 324,10 @@ mode_cfg_subtypes(struct mode_cfg *cfg, const char *str)
	/* Parse and configure CHANMODE subtypes, e.g.:
	 *
	 * "abc,d,ef,xyz" sets mode bits:
	 *  - A = a | b | c
	 *  - A = abc
	 *  - B = d
	 *  - C = e | f
	 *  - D = x | y | z
	 *  - C = ef
	 *  - D = xyz
	 */

	char c;


@@ 539,20 339,17 @@ mode_cfg_subtypes(struct mode_cfg *cfg, const char *str)
		&(cfg->CHANMODES.D)
	};

	struct mode duplicates, *setting = subtypes[0];
	struct mode *setting = subtypes[0];

	memset(&(cfg->CHANMODES.A), 0, sizeof (struct mode));
	memset(&(cfg->CHANMODES.B), 0, sizeof (struct mode));
	memset(&(cfg->CHANMODES.C), 0, sizeof (struct mode));
	memset(&(cfg->CHANMODES.D), 0, sizeof (struct mode));
	memset(&duplicates, 0, sizeof (struct mode));
	memset(&(cfg->CHANMODES.A), 0, sizeof(struct mode));
	memset(&(cfg->CHANMODES.B), 0, sizeof(struct mode));
	memset(&(cfg->CHANMODES.C), 0, sizeof(struct mode));
	memset(&(cfg->CHANMODES.D), 0, sizeof(struct mode));

	unsigned commas = 0;

	while ((c = *str++)) {

		uint32_t bit;

		if (c == ',') {
			switch (commas) {
				case 0:


@@ 561,26 358,26 @@ mode_cfg_subtypes(struct mode_cfg *cfg, const char *str)
					setting = subtypes[++commas];
					continue;
				default:
					return MODE_ERR_INVALID_CONFIG;
					goto error;
			}
		}

		if ((bit = flag_bit(c)) == 0)
			continue; /* TODO: aggregate warnings, invalid flag */

		if (mode_isset(&duplicates, c))
			continue; /* TODO: aggregate warnings, duplicate flag */
		if (!mode_bit(c))
			goto error;

		if (MODE_ISLOWER(c)) {
			MODE_SET(duplicates.lower, bit, MODE_SET_ON);
			MODE_SET(setting->lower, bit, MODE_SET_ON);
		} else {
			MODE_SET(duplicates.upper, bit, MODE_SET_ON);
			MODE_SET(setting->upper, bit, MODE_SET_ON);
		}
		mode_set(setting, c, 1);
	}

	return MODE_ERR_NONE;

error:

	memset(&(cfg->CHANMODES.A), 0, sizeof(struct mode));
	memset(&(cfg->CHANMODES.B), 0, sizeof(struct mode));
	memset(&(cfg->CHANMODES.C), 0, sizeof(struct mode));
	memset(&(cfg->CHANMODES.D), 0, sizeof(struct mode));

	return MODE_ERR_INVALID_CONFIG;
}

static enum mode_err


@@ 594,17 391,16 @@ mode_cfg_prefix(struct mode_cfg *cfg, const char *str)
	 *  - c -> #
	 */

	char *str_f, cf,
	     *str_t, ct,
	     *cfg_f = cfg->PREFIX.F,
	     *cfg_t = cfg->PREFIX.T,
	     _str[strlen(str) + 1];

	struct mode duplicates;
	char *cfg_f = cfg->PREFIX.F;
	char *cfg_t = cfg->PREFIX.T;
	char *dup = strdup(str);
	char *str_f;
	char *str_t;

	memcpy(_str, str, sizeof(_str));
	memset(cfg->PREFIX.F, 0, sizeof(cfg->PREFIX.F));
	memset(cfg->PREFIX.T, 0, sizeof(cfg->PREFIX.T));

	if (*(str_f = _str) != '(')
	if (*(str_f = dup) != '(')
		goto error;

	if (!(str_t = strchr(str_f, ')')))


@@ 616,32 412,20 @@ mode_cfg_prefix(struct mode_cfg *cfg, const char *str)
	if (strlen(str_f) != strlen(str_t))
		goto error;

	memset(&duplicates, 0, sizeof duplicates);

	while (*str_f) {

		uint32_t bit;

		cf = *str_f++;
		ct = *str_t++;
		char cf = *str_f++;
		char ct = *str_t++;

		/* Check printable prefix */
		if (!(isgraph(ct)))
		if (!mode_bit(cf))
			goto error;

		/* Check valid flag */
		if ((bit = flag_bit(cf)) == 0)
		if (!(isgraph(ct)))
			goto error;

		/* Check duplicates */
		if (mode_isset(&duplicates, cf))
		if (strchr(cfg->PREFIX.F, cf))
			goto error;

		if (MODE_ISLOWER(cf))
			MODE_SET(duplicates.lower, bit, MODE_SET_ON);
		else
			MODE_SET(duplicates.upper, bit, MODE_SET_ON);

		*cfg_f++ = cf;
		*cfg_t++ = ct;
	}


@@ 649,6 433,8 @@ mode_cfg_prefix(struct mode_cfg *cfg, const char *str)
	*cfg_f = 0;
	*cfg_t = 0;

	free(dup);

	return MODE_ERR_NONE;

error:


@@ 656,38 442,21 @@ error:
	*(cfg->PREFIX.F) = 0;
	*(cfg->PREFIX.T) = 0;

	return MODE_ERR_INVALID_CONFIG;
}

static enum mode_err
mode_cfg_modes(struct mode_cfg *cfg, const char *str)
{
	/* Parse and configure MODES, valid values are numeric strings [1-99] */

	unsigned modes = 0;

	for (; modes < 100 && *str; str++) {
		if (isdigit(*str))
			modes = modes * 10 + (*str - '0');
		else
			return MODE_ERR_INVALID_CONFIG;
	}
	free(dup);

	if (!(modes > 0 && modes < 100))
		return MODE_ERR_INVALID_CONFIG;

	cfg->MODES = modes;

	return MODE_ERR_NONE;
	return MODE_ERR_INVALID_CONFIG;
}

enum chanmode_flag_type
chanmode_type(const struct mode_cfg *cfg, enum mode_set set, int flag)
enum mode_type
mode_type(const struct mode_cfg *cfg, int flag, int set)
{
	/* Return the chanmode flag type specified by config */

	if (!(set == MODE_SET_ON || set == MODE_SET_OFF))
		return MODE_FLAG_INVALID_SET;
	/* Return the chanmode flag type specified by config
	 *
	 *   A = Always has a parameter.
	 *   B = Always has a parameter.
	 *   C = Only has a parameter when set.
	 *   D = Never has a parameter.
	 */

	if (mode_isset(&(cfg->chanmodes), flag)) {



@@ 700,14 469,8 @@ chanmode_type(const struct mode_cfg *cfg, enum mode_set set, int flag)
		if (mode_isset(&(cfg->CHANMODES.B), flag))
			return MODE_FLAG_CHANMODE_PARAM;

		if (mode_isset(&(cfg->CHANMODES.C), flag)) {

			if (set == MODE_SET_ON)
				return MODE_FLAG_CHANMODE_PARAM;

			if (set == MODE_SET_OFF)
				return MODE_FLAG_CHANMODE;
		}
		if (mode_isset(&(cfg->CHANMODES.C), flag))
			return (set ? MODE_FLAG_CHANMODE_PARAM : MODE_FLAG_CHANMODE);

		if (mode_isset(&(cfg->CHANMODES.D), flag))
			return MODE_FLAG_CHANMODE;

M src/components/mode.h => src/components/mode.h +16 -34
@@ 30,9 30,6 @@
 *
 * PREFIX modes are not included in CHANMODES
 *
 * MODES specifies the maximum number of channel modes with parameter
 * allowed per MODE command
 *
 * Numeric 353 (RPL_NAMREPLY) sets chanmode and prfxmode for users on a channel
 * by providing the prefix character rather than the flag
 */


@@ 42,20 39,13 @@
/* [azAZ] */
#define MODE_STR_LEN 26 * 2

#define MODE_EMPTY (struct mode) \
{                \
    .prefix = 0, \
    .lower  = 0, \
    .upper  = 0, \
}
#define MODE_EMPTY (struct mode) { 0 }

enum mode_err
{
	MODE_ERR_DUPLICATE      = -5,
	MODE_ERR_INVALID_CONFIG = -4,
	MODE_ERR_INVALID_PREFIX = -3,
	MODE_ERR_INVALID_FLAG   = -2,
	MODE_ERR_INVALID_SET    = -1,
	MODE_ERR_INVALID_CONFIG = -3,
	MODE_ERR_INVALID_PREFIX = -2,
	MODE_ERR_INVALID_FLAG   = -1,
	MODE_ERR_NONE
};



@@ 64,22 54,13 @@ enum mode_cfg_type
	MODE_CFG_DEFAULTS,  /* Set RFC2811 mode defaults */
	MODE_CFG_CHANMODES, /* Set numeric 004 chanmdoes string */
	MODE_CFG_USERMODES, /* Set numeric 004 usermodes string */
	MODE_CFG_PREFIX,    /* Set numeric 005 PREFIX */
	MODE_CFG_SUBTYPES,  /* Set numeric 005 CHANMODES subtypes */
	MODE_CFG_MODES,     /* Set numeric 005 MODES */
};

enum mode_set
{
	MODE_SET_OFF = 0,
	MODE_SET_ON = 1,
	MODE_SET_INVALID,
	MODE_CFG_PREFIX,    /* Set numeric 005 PREFIX */
};

enum chanmode_flag_type
enum mode_type
{
	MODE_FLAG_INVALID_FLAG,
	MODE_FLAG_INVALID_SET,
	MODE_FLAG_CHANMODE,       /* Chanmode flag without parameter */
	MODE_FLAG_CHANMODE_PARAM, /* Chanmode flag with parameter */
	MODE_FLAG_PREFIX,         /* Chanmode flag that sets prfxmode */


@@ 94,12 75,11 @@ struct mode

struct mode_cfg
{
	unsigned MODES;        /* Numeric 005 MODES */
	struct mode chanmodes; /* Numeric 004 chanmodes string */
	struct mode usermodes; /* Numeric 004 usermodes string */
	struct
	struct                 /* Numeric 005 CHANMODES substrings */
	{
		struct mode A; /* Numeric 005 CHANMODES substrings */
		struct mode A;
		struct mode B;
		struct mode C;
		struct mode D;


@@ 120,18 100,20 @@ struct mode_str
		MODE_STR_CHANMODE,
		MODE_STR_USERMODE,
		MODE_STR_PRFXMODE,
		MODE_STR_T_SIZE
	} type;
};


const char* mode_str(const struct mode*, struct mode_str*);
enum chanmode_flag_type chanmode_type(const struct mode_cfg*, enum mode_set, int);

enum mode_err mode_cfg(struct mode_cfg*, const char*, enum mode_cfg_type);
enum mode_err mode_chanmode_prefix(struct mode*, const struct mode_cfg*, int);
enum mode_err mode_chanmode_set(struct mode*, const struct mode_cfg*, int, enum mode_set);
enum mode_err mode_chanmode_set(struct mode*, const struct mode_cfg*, int, int);
enum mode_err mode_prfxmode_prefix(struct mode*, const struct mode_cfg*, int);
enum mode_err mode_prfxmode_set(struct mode*, const struct mode_cfg*, int, enum mode_set);
enum mode_err mode_usermode_set(struct mode*, const struct mode_cfg*, int, enum mode_set);
enum mode_err mode_prfxmode_set(struct mode*, const struct mode_cfg*, int, int);
enum mode_err mode_usermode_set(struct mode*, const struct mode_cfg*, int, int);

enum mode_type mode_type(const struct mode_cfg*, int, int);

void mode_reset(struct mode*, struct mode_str*);

#endif

M src/components/server.c => src/components/server.c +0 -7
@@ 12,7 12,6 @@
#define HANDLED_005 \
	X(CASEMAPPING)  \
	X(CHANMODES)    \
	X(MODES)        \
	X(PREFIX)

struct opt


@@ 454,12 453,6 @@ server_set_CHANMODES(struct server *s, char *val)
}

static int
server_set_MODES(struct server *s, char *val)
{
	return mode_cfg(&(s->mode_cfg), val, MODE_CFG_MODES) != MODE_ERR_NONE;
}

static int
server_set_PREFIX(struct server *s, char *val)
{
	return mode_cfg(&(s->mode_cfg), val, MODE_CFG_PREFIX) != MODE_ERR_NONE;

M src/handlers/irc_recv.c => src/handlers/irc_recv.c +43 -49
@@ 525,16 525,15 @@ irc_numeric_333(struct server *s, struct irc_message *m)
static int
irc_numeric_353(struct server *s, struct irc_message *m)
{
	/* 353 <nick> <type> <channel> 1*(<modes><nick>) */
	/* <type> <channel> 1*(<modes><nick>) */

	char *chan;
	char *nick;
	char *nicks;
	char *prfx;
	char *type;
	char *prefix;
	struct channel *c;

	if (!irc_message_param(m, &type))
	if (!irc_message_param(m, &prefix))
		failf(s, "RPL_NAMEREPLY: type is null");

	if (!irc_message_param(m, &chan))


@@ 546,10 545,18 @@ irc_numeric_353(struct server *s, struct irc_message *m)
	if ((c = channel_list_get(&s->clist, chan, s->casemapping)) == NULL)
		failf(s, "RPL_NAMEREPLY: channel '%s' not found", chan);

	if (mode_chanmode_prefix(&(c->chanmodes), &(s->mode_cfg), *type) != MODE_ERR_NONE)
		failf(s, "RPL_NAMEREPLY: invalid channel flag: '%c'", *type);
	if (*prefix != '@' && *prefix != '*' && *prefix != '=')
		failf(s, "RPL_NAMEREPLY: invalid channel type: '%c'", *prefix);

	while ((prfx = nick = irc_strsep(&nicks))) {
	if (*prefix == '@')
		(void) mode_chanmode_set(&(c->chanmodes), &(s->mode_cfg), 's', 1);

	if (*prefix == '*')
		(void) mode_chanmode_set(&(c->chanmodes), &(s->mode_cfg), 'p', 1);

	c->chanmodes.prefix = *prefix;

	while ((prefix = nick = irc_strsep(&nicks))) {

		struct mode m = MODE_EMPTY;



@@ 557,7 564,7 @@ irc_numeric_353(struct server *s, struct irc_message *m)
			nick++;

		if (*nick == 0)
			failf(s, "RPL_NAMEREPLY: invalid nick: '%s'", prfx);
			failf(s, "RPL_NAMEREPLY: invalid nick: '%s'", prefix);

		if (user_list_add(&(c->users), s->casemapping, nick, m) == USER_ERR_DUPLICATE)
			failf(s, "RPL_NAMEREPLY: duplicate nick: '%s'", nick);


@@ 883,7 890,6 @@ recv_mode_chanmodes(struct irc_message *m, const struct mode_cfg *cfg, struct se
	char *modestring;
	char *modearg;
	enum mode_err mode_err;
	enum mode_set mode_set;
	struct mode *chanmodes = &(c->chanmodes);
	struct user *user;



@@ 895,36 901,41 @@ recv_mode_chanmodes(struct irc_message *m, const struct mode_cfg *cfg, struct se
	}

	do {
		mode_set = MODE_SET_INVALID;
		int set = -1;
		mode_err = MODE_ERR_NONE;

		while ((flag = *modestring++)) {

			if (flag == '-') {
				set = 0;
				continue;
			}

			if (flag == '+') {
				mode_set = MODE_SET_ON;
				set = 1;
				continue;
			}

			if (flag == '-') {
				mode_set = MODE_SET_OFF;
			if (set == -1) {
				newlinef(c, 0, FROM_ERROR, "MODE: missing '+'/'-'");
				continue;
			}

			modearg = NULL;

			switch (chanmode_type(cfg, mode_set, flag)) {
			switch (mode_type(cfg, flag, set)) {

				/* Doesn't consume an argument */
				case MODE_FLAG_CHANMODE:

					mode_err = mode_chanmode_set(chanmodes, cfg, flag, mode_set);
					mode_err = mode_chanmode_set(chanmodes, cfg, flag, set);

					if (mode_err == MODE_ERR_NONE) {
						newlinef(c, 0, FROM_INFO, "%s%s%s mode: %c%c",
								(m->from ? m->from : ""),
								(m->from ? " set " : ""),
								c->name,
								(mode_set == MODE_SET_ON ? '+' : '-'),
								(set ? '+' : '-'),
								flag);
					}
					break;


@@ 937,14 948,14 @@ recv_mode_chanmodes(struct irc_message *m, const struct mode_cfg *cfg, struct se
						continue;
					}

					mode_err = mode_chanmode_set(chanmodes, cfg, flag, mode_set);
					mode_err = mode_chanmode_set(chanmodes, cfg, flag, set);

					if (mode_err == MODE_ERR_NONE) {
						newlinef(c, 0, FROM_INFO, "%s%s%s mode: %c%c %s",
								(m->from ? m->from : ""),
								(m->from ? " set " : ""),
								c->name,
								(mode_set == MODE_SET_ON ? '+' : '-'),
								(set ? '+' : '-'),
								flag,
								modearg);
					}


@@ 963,44 974,26 @@ recv_mode_chanmodes(struct irc_message *m, const struct mode_cfg *cfg, struct se
						continue;
					}

					mode_prfxmode_set(&(user->prfxmodes), cfg, flag, mode_set);
					mode_prfxmode_set(&(user->prfxmodes), cfg, flag, set);

					if (mode_err == MODE_ERR_NONE) {
						newlinef(c, 0, FROM_INFO, "%s%suser %s mode: %c%c",
								(m->from ? m->from : ""),
								(m->from ? " set " : ""),
								modearg,
								(mode_set == MODE_SET_ON ? '+' : '-'),
								(set ? '+' : '-'),
								flag);
					}
					break;

				case MODE_FLAG_INVALID_SET:
					mode_err = MODE_ERR_INVALID_SET;
					break;

				case MODE_FLAG_INVALID_FLAG:
					mode_err = MODE_ERR_INVALID_FLAG;
					newlinef(c, 0, FROM_ERROR, "MODE: invalid flag '%c'", flag);
					break;

				default:
					newlinef(c, 0, FROM_ERROR, "MODE: unhandled error, flag '%c'", flag);
					continue;
			}

			switch (mode_err) {

				case MODE_ERR_INVALID_FLAG:
					newlinef(c, 0, FROM_ERROR, "MODE: invalid flag '%c'", flag);
					break;

				case MODE_ERR_INVALID_SET:
					newlinef(c, 0, FROM_ERROR, "MODE: missing '+'/'-'");
					break;

				default:
					break;
			}
		}
	} while (irc_message_param(m, &modestring));



@@ 1016,39 1009,40 @@ recv_mode_usermodes(struct irc_message *m, const struct mode_cfg *cfg, struct se
	char flag;
	char *modestring;
	enum mode_err mode_err;
	enum mode_set mode_set;
	struct mode *usermodes = &(s->usermodes);

	if (!irc_message_param(m, &modestring))
		failf(s, "MODE: modestring is null");

	do {
		mode_set = MODE_SET_INVALID;
		int set = -1;

		while ((flag = *modestring++)) {

			if (flag == '-') {
				set = 0;
				continue;
			}

			if (flag == '+') {
				mode_set = MODE_SET_ON;
				set = 1;
				continue;
			}

			if (flag == '-') {
				mode_set = MODE_SET_OFF;
			if (set == -1) {
				server_error(s, "MODE: missing '+'/'-'");
				continue;
			}

			mode_err = mode_usermode_set(usermodes, cfg, flag, mode_set);
			mode_err = mode_usermode_set(usermodes, cfg, flag, set);

			if (mode_err == MODE_ERR_NONE)
				server_info(s, "%s%smode: %c%c",
						(m->from ? m->from : ""),
						(m->from ? " set " : ""),
						(mode_set == MODE_SET_ON ? '+' : '-'),
						(set ? '+' : '-'),
						flag);

			else if (mode_err == MODE_ERR_INVALID_SET)
				server_error(s, "MODE: missing '+'/'-'");

			else if (mode_err == MODE_ERR_INVALID_FLAG)
				server_error(s, "MODE: invalid flag '%c'", flag);
		}

M test/components/mode.c => test/components/mode.c +109 -239
@@ 5,19 5,19 @@
#define ALL_UPPERS "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

static void
test_flag_bit(void)
test_mode_bit(void)
{
	/* Test mode flag -> mode bit */

	assert_eq(flag_bit('a' - 1), 0);
	assert_eq(flag_bit('a'),     1 << 0);
	assert_eq(flag_bit('z'),     1 << 25);
	assert_eq(flag_bit('z' + 1), 0);
	assert_eq(mode_bit('a' - 1), 0);
	assert_eq(mode_bit('a'),     1 << 0);
	assert_eq(mode_bit('z'),     1 << 25);
	assert_eq(mode_bit('z' + 1), 0);

	assert_eq(flag_bit('A' - 1), 0);
	assert_eq(flag_bit('A'),     1 << 0);
	assert_eq(flag_bit('Z'),     1 << 25);
	assert_eq(flag_bit('Z' + 1), 0);
	assert_eq(mode_bit('A' - 1), 0);
	assert_eq(mode_bit('A'),     1 << 0);
	assert_eq(mode_bit('Z'),     1 << 25);
	assert_eq(mode_bit('Z' + 1), 0);
}

static void


@@ 33,7 33,7 @@ test_mode_str(void)
	/* mode_str type not set */
	assert_fatal(mode_str(&m, &str));

	str.type = MODE_STR_T_SIZE;
	str.type = -1;

	/* mode_str type unknown */
	assert_fatal(mode_str(&m, &str));


@@ 60,9 60,9 @@ test_mode_str(void)
}

static void
test_chanmode_set(void)
test_mode_chanmode_set(void)
{
	/* Test setting/unsetting chanmode flag, prefix and mode string */
	/* Test setting/unsetting chanmode flags */

	struct mode m = MODE_EMPTY;
	struct mode_cfg cfg;


@@ 71,79 71,67 @@ test_chanmode_set(void)
	mode_cfg_chanmodes(&cfg, "abcdefghijsp");
	mode_cfg_subtypes(&cfg, "abc,def,ghi,jsp");

#define CHECK(STR, PRFX) \
	assert_strcmp(mode_str(&m, &str), (STR)); assert_eq(m.prefix, (PRFX));

	/* Test setting/unsetting invalid chanmode flag */
	assert_eq(mode_chanmode_set(&m, &cfg, 'z', MODE_SET_ON), MODE_ERR_INVALID_FLAG);
	CHECK("", 0);
	assert_eq(mode_chanmode_set(&m, &cfg, 'z', MODE_SET_OFF), MODE_ERR_INVALID_FLAG);
	CHECK("", 0);
	assert_eq(mode_chanmode_set(&m, &cfg, 'z', 1), MODE_ERR_INVALID_FLAG);
	assert_strcmp(mode_str(&m, &str), "");

	assert_eq(mode_chanmode_set(&m, &cfg, 'z', 0), MODE_ERR_INVALID_FLAG);
	assert_strcmp(mode_str(&m, &str), "");

	/* Test valid CHANMODE subtype A doesn't set flag */
	assert_eq(mode_chanmode_set(&m, &cfg, 'a', MODE_SET_ON), MODE_ERR_NONE);
	CHECK("", 0);
	assert_eq(mode_chanmode_set(&m, &cfg, 'a', 1), MODE_ERR_NONE);
	assert_strcmp(mode_str(&m, &str), "");

	/* Test valid CHANMODE subtypes B,C,D set flags */
	assert_eq(mode_chanmode_set(&m, &cfg, 'd', MODE_SET_ON), MODE_ERR_NONE);
	CHECK("d", 0);

	assert_eq(mode_chanmode_set(&m, &cfg, 'e', MODE_SET_ON), MODE_ERR_NONE);
	CHECK("de", 0);

	assert_eq(mode_chanmode_set(&m, &cfg, 'j', MODE_SET_ON), MODE_ERR_NONE);
	CHECK("dej", 0);

	/* Test 's'/'p' chanmodes set prefix but do not appear in mode string
	 *
	 * Ensure 's' always supercedes and unsets 'p',
	 * Ensure 'p' is silently ignored when 's' is set */

	assert_eq(mode_chanmode_set(&m, &cfg, 'p', MODE_SET_ON), MODE_ERR_NONE);
	CHECK("dejp", MODE_CHANMODE_PREFIX_PRIVATE);
	assert_true(mode_isset(&m, 'p'));
	assert_false(mode_isset(&m, 's'));

	/* Unsetting 'p' sets default */
	assert_eq(mode_chanmode_set(&m, &cfg, 'p', MODE_SET_OFF), MODE_ERR_NONE);
	CHECK("dej", MODE_CHANMODE_PREFIX_OTHER);
	assert_false(mode_isset(&m, 'p'));
	assert_false(mode_isset(&m, 's'));

	/* 's' supercedes 'p' */
	assert_eq(mode_chanmode_set(&m, &cfg, 'p', MODE_SET_ON), MODE_ERR_NONE);
	assert_eq(mode_chanmode_set(&m, &cfg, 's', MODE_SET_ON), MODE_ERR_NONE);
	CHECK("dejs", MODE_CHANMODE_PREFIX_SECRET);
	assert_true(mode_isset(&m, 's'));
	assert_false(mode_isset(&m, 'p'));

	/* 'p' is silently ignored when 's' is set */
	assert_eq(mode_chanmode_set(&m, &cfg, 'p', MODE_SET_ON), MODE_ERR_NONE);
	CHECK("dejs", MODE_CHANMODE_PREFIX_SECRET);
	assert_true(mode_isset(&m, 's'));
	assert_false(mode_isset(&m, 'p'));

	/* Unsetting 's' sets default */
	assert_eq(mode_chanmode_set(&m, &cfg, 's', MODE_SET_OFF), MODE_ERR_NONE);
	CHECK("dej", MODE_CHANMODE_PREFIX_OTHER);
	assert_false(mode_isset(&m, 'p'));
	assert_false(mode_isset(&m, 's'));

	/* Test unsetting previous flags */
	assert_eq(mode_chanmode_set(&m, &cfg, 'j', MODE_SET_OFF), MODE_ERR_NONE);
	CHECK("de", MODE_CHANMODE_PREFIX_OTHER);

	assert_eq(mode_chanmode_set(&m, &cfg, 'e', MODE_SET_OFF), MODE_ERR_NONE);
	CHECK("d", MODE_CHANMODE_PREFIX_OTHER);

	assert_eq(mode_chanmode_set(&m, &cfg, 'd', MODE_SET_OFF), MODE_ERR_NONE);
	CHECK("", MODE_CHANMODE_PREFIX_OTHER);
	assert_eq(mode_chanmode_set(&m, &cfg, 'd', 1), MODE_ERR_NONE);
	assert_strcmp(mode_str(&m, &str), "d");

#undef CHECK
	assert_eq(mode_chanmode_set(&m, &cfg, 'e', 1), MODE_ERR_NONE);
	assert_strcmp(mode_str(&m, &str), "de");

	assert_eq(mode_chanmode_set(&m, &cfg, 'j', 1), MODE_ERR_NONE);
	assert_strcmp(mode_str(&m, &str), "dej");

	/* test unsetting flags */
	assert_eq(mode_chanmode_set(&m, &cfg, 's', 0), MODE_ERR_NONE);
	assert_eq(mode_chanmode_set(&m, &cfg, 'j', 0), MODE_ERR_NONE);
	assert_eq(mode_chanmode_set(&m, &cfg, 'e', 0), MODE_ERR_NONE);
	assert_eq(mode_chanmode_set(&m, &cfg, 'd', 0), MODE_ERR_NONE);
	assert_strcmp(mode_str(&m, &str), "");
}

static void
test_prfxmode_set(void)
test_mode_prfxmode_prefix(void)
{
	/* Test setting prfxmode by prefix */

	struct mode m = MODE_EMPTY;
	struct mode_str str = { .type = MODE_STR_PRFXMODE };

	struct mode_cfg cfg = {
		.PREFIX = {
			.F = "abc",
			.T = "123"
		}
	};

	mode_cfg_chanmodes(&cfg, "abc");

	/* Test setting invalid prfxmode prefix */
	assert_eq(mode_prfxmode_prefix(&m, &cfg, 0),   MODE_ERR_INVALID_PREFIX);
	assert_eq(mode_prfxmode_prefix(&m, &cfg, '0'), MODE_ERR_INVALID_PREFIX);
	assert_eq(mode_prfxmode_prefix(&m, &cfg, '4'), MODE_ERR_INVALID_PREFIX);

	/* Test setting valid prfxmode prefix */
	assert_eq(mode_prfxmode_prefix(&m, &cfg, '2'), MODE_ERR_NONE);
	assert_eq(mode_prfxmode_prefix(&m, &cfg, '3'), MODE_ERR_NONE);

	assert_strcmp(mode_str(&m, &str), "bc");
	assert_eq(m.prefix, '2');
}

static void
test_mode_prfxmode_set(void)
{
	/* Test setting/unsetting prfxmode flag and prefix */



@@ 159,30 147,30 @@ test_prfxmode_set(void)
	mode_cfg_chanmodes(&cfg, "abc");

	/* Test setting/unsetting invalid prfxmode flag */
	assert_eq(mode_prfxmode_set(&m, &cfg, 'd', MODE_SET_ON), MODE_ERR_INVALID_FLAG);
	assert_eq(mode_prfxmode_set(&m, &cfg, 'd', 1), MODE_ERR_INVALID_FLAG);
	assert_eq(m.prefix, 0);
	assert_eq(mode_prfxmode_set(&m, &cfg, 'd', MODE_SET_OFF), MODE_ERR_INVALID_FLAG);
	assert_eq(mode_prfxmode_set(&m, &cfg, 'd', 0), MODE_ERR_INVALID_FLAG);
	assert_eq(m.prefix, 0);

	/* Test setting valid flags respects PREFIX precedence */
	assert_eq(mode_prfxmode_set(&m, &cfg, 'b', MODE_SET_ON), MODE_ERR_NONE);
	assert_eq(mode_prfxmode_set(&m, &cfg, 'b', 1), MODE_ERR_NONE);
	assert_eq(m.prefix, '2');
	assert_eq(mode_prfxmode_set(&m, &cfg, 'c', MODE_SET_ON), MODE_ERR_NONE);
	assert_eq(mode_prfxmode_set(&m, &cfg, 'c', 1), MODE_ERR_NONE);
	assert_eq(m.prefix, '2');
	assert_eq(mode_prfxmode_set(&m, &cfg, 'a', MODE_SET_ON), MODE_ERR_NONE);
	assert_eq(mode_prfxmode_set(&m, &cfg, 'a', 1), MODE_ERR_NONE);
	assert_eq(m.prefix, '1');

	/* Test unsetting valid flags respects PREFIX precedence */
	assert_eq(mode_prfxmode_set(&m, &cfg, 'b', MODE_SET_OFF), MODE_ERR_NONE);
	assert_eq(mode_prfxmode_set(&m, &cfg, 'b', 0), MODE_ERR_NONE);
	assert_eq(m.prefix, '1');
	assert_eq(mode_prfxmode_set(&m, &cfg, 'a', MODE_SET_OFF), MODE_ERR_NONE);
	assert_eq(mode_prfxmode_set(&m, &cfg, 'a', 0), MODE_ERR_NONE);
	assert_eq(m.prefix, '3');
	assert_eq(mode_prfxmode_set(&m, &cfg, 'c', MODE_SET_OFF), MODE_ERR_NONE);
	assert_eq(mode_prfxmode_set(&m, &cfg, 'c', 0), MODE_ERR_NONE);
	assert_eq(m.prefix, 0);
}

static void
test_usermode_set(void)
test_mode_usermode_set(void)
{
	/* Test setting/unsetting usermode flag and mode string */



@@ 193,112 181,31 @@ test_usermode_set(void)
	mode_cfg_usermodes(&cfg, "azAZ");

	/* Test setting invalid usermode flag */
	assert_eq(mode_usermode_set(&m, &cfg, 'b', MODE_SET_ON), MODE_ERR_INVALID_FLAG);
	assert_eq(mode_usermode_set(&m, &cfg, 'b', 1), MODE_ERR_INVALID_FLAG);

	/* Test setting valid flags */
	assert_eq(mode_usermode_set(&m, &cfg, 'a', MODE_SET_ON), MODE_ERR_NONE);
	assert_eq(mode_usermode_set(&m, &cfg, 'Z', MODE_SET_ON), MODE_ERR_NONE);
	assert_eq(mode_usermode_set(&m, &cfg, 'a', 1), MODE_ERR_NONE);
	assert_eq(mode_usermode_set(&m, &cfg, 'Z', 1), MODE_ERR_NONE);
	assert_strcmp(mode_str(&m, &str), "aZ");

	assert_eq(mode_usermode_set(&m, &cfg, 'z', MODE_SET_ON), MODE_ERR_NONE);
	assert_eq(mode_usermode_set(&m, &cfg, 'A', MODE_SET_ON), MODE_ERR_NONE);
	assert_eq(mode_usermode_set(&m, &cfg, 'z', 1), MODE_ERR_NONE);
	assert_eq(mode_usermode_set(&m, &cfg, 'A', 1), MODE_ERR_NONE);
	assert_strcmp(mode_str(&m, &str), "azAZ");

	/* Test unsetting invalid usermode flag */
	assert_eq(mode_usermode_set(&m, &cfg, 'c', MODE_SET_OFF), MODE_ERR_INVALID_FLAG);
	assert_eq(mode_usermode_set(&m, &cfg, 'c', 0), MODE_ERR_INVALID_FLAG);

	/* Test unsetting valid flags */
	assert_eq(mode_usermode_set(&m, &cfg, 'z', MODE_SET_OFF), MODE_ERR_NONE);
	assert_eq(mode_usermode_set(&m, &cfg, 'Z', MODE_SET_OFF), MODE_ERR_NONE);
	assert_eq(mode_usermode_set(&m, &cfg, 'z', 0), MODE_ERR_NONE);
	assert_eq(mode_usermode_set(&m, &cfg, 'Z', 0), MODE_ERR_NONE);
	assert_strcmp(mode_str(&m, &str), "aA");

	assert_eq(mode_usermode_set(&m, &cfg, 'a', MODE_SET_OFF), MODE_ERR_NONE);
	assert_eq(mode_usermode_set(&m, &cfg, 'A', MODE_SET_OFF), MODE_ERR_NONE);
	assert_eq(mode_usermode_set(&m, &cfg, 'a', 0), MODE_ERR_NONE);
	assert_eq(mode_usermode_set(&m, &cfg, 'A', 0), MODE_ERR_NONE);
	assert_strcmp(mode_str(&m, &str), "");
}

static void
test_chanmode_prefix(void)
{
	/* Test setting chanmode by prefix */

	struct mode m = MODE_EMPTY;
	struct mode_cfg cfg;
	struct mode_str str = { .type = MODE_STR_CHANMODE };

	mode_cfg_chanmodes(&cfg, "sp");

#define CHECK(M, PRFX, P, S, STR) \
	assert_eq((M).prefix, (PRFX));         \
	assert_eq(mode_isset(&(M), 'p'), (P)); \
	assert_eq(mode_isset(&(M), 's'), (S)); \
	assert_strcmp(mode_str(&(M), &str), (STR));

	/* Test setting invalid chanmode prefix */
	assert_eq(mode_chanmode_prefix(&m, &cfg, '$'), MODE_ERR_INVALID_PREFIX);
	CHECK(m, 0, 0, 0, "");

	/* Test setting valid chanmode prefixes by precedence*/
	assert_eq(mode_chanmode_prefix(&m, &cfg, MODE_CHANMODE_PREFIX_OTHER), MODE_ERR_NONE);
	CHECK(m, MODE_CHANMODE_PREFIX_OTHER, 0, 0, "");

	assert_eq(mode_chanmode_prefix(&m, &cfg, MODE_CHANMODE_PREFIX_PRIVATE), MODE_ERR_NONE);
	CHECK(m, MODE_CHANMODE_PREFIX_PRIVATE, 1, 0, "p");

	assert_eq(mode_chanmode_prefix(&m, &cfg, MODE_CHANMODE_PREFIX_SECRET), MODE_ERR_NONE);
	CHECK(m, MODE_CHANMODE_PREFIX_SECRET, 0, 1, "s");

	/* Test silently ignored setting by precedence */

	/* PRIVATE > OTHER */
	struct mode m2 = MODE_EMPTY;

	assert_eq(mode_chanmode_prefix(&m2, &cfg, MODE_CHANMODE_PREFIX_PRIVATE), MODE_ERR_NONE);
	assert_eq(mode_chanmode_prefix(&m2, &cfg, MODE_CHANMODE_PREFIX_OTHER), MODE_ERR_NONE);
	CHECK(m2, MODE_CHANMODE_PREFIX_PRIVATE, 1, 0, "p");

	/* SECRET > PRIVATE, OTHER */
	struct mode m3 = MODE_EMPTY;

	assert_eq(mode_chanmode_prefix(&m3, &cfg, MODE_CHANMODE_PREFIX_SECRET), MODE_ERR_NONE);
	assert_eq(mode_chanmode_prefix(&m3, &cfg, MODE_CHANMODE_PREFIX_PRIVATE), MODE_ERR_NONE);
	assert_eq(mode_chanmode_prefix(&m3, &cfg, MODE_CHANMODE_PREFIX_OTHER), MODE_ERR_NONE);
	CHECK(m3, MODE_CHANMODE_PREFIX_SECRET, 0, 1, "s");

#undef CHECK
}

static void
test_prfxmode_prefix(void)
{
	/* Test setting prfxmode by prefix */

	struct mode m = MODE_EMPTY;
	struct mode_str str = { .type = MODE_STR_PRFXMODE };

	struct mode_cfg cfg = {
		.PREFIX = {
			.F = "abc",
			.T = "123"
		}
	};

	mode_cfg_chanmodes(&cfg, "abc");

	/* Test setting invalid prfxmode prefix */
	assert_eq(mode_prfxmode_prefix(&m, &cfg, 0),   MODE_ERR_INVALID_PREFIX);
	assert_eq(mode_prfxmode_prefix(&m, &cfg, '0'), MODE_ERR_INVALID_PREFIX);
	assert_eq(mode_prfxmode_prefix(&m, &cfg, '4'), MODE_ERR_INVALID_PREFIX);

	/* Test setting valid prfxmode prefix */
	assert_eq(mode_prfxmode_prefix(&m, &cfg, '2'), MODE_ERR_NONE);
	assert_eq(mode_prfxmode_prefix(&m, &cfg, '3'), MODE_ERR_NONE);

	assert_strcmp(mode_str(&m, &str), "bc");
	assert_eq(m.prefix, '2');
}

static void
test_mode_cfg_usermodes(void)
{
	/* Test configuring server usermodes */


@@ 372,15 279,11 @@ test_mode_cfg_subtypes(void)

	/* Test extra commas */
	assert_eq(mode_cfg_subtypes(&cfg, "abc,def,,xyz,,,abc"), MODE_ERR_INVALID_CONFIG);
	CHECK("abc", "def", "", "xyz");
	CHECK("", "", "", "");

	/* Test invalid flags */
	assert_eq(mode_cfg_subtypes(&cfg, "!!abc,d123e,fg!-@,^&"), MODE_ERR_NONE);
	CHECK("abc", "de", "fg", "");

	/* Test duplicate flags */
	assert_eq(mode_cfg_subtypes(&cfg, "zaabc,deefz,zghh,zzz"), MODE_ERR_NONE);
	CHECK("abcz", "def", "gh", "");
	assert_eq(mode_cfg_subtypes(&cfg, "!!abc,d123e,fg!-@,^&"), MODE_ERR_INVALID_CONFIG);
	CHECK("", "", "", "");

	const char *all_flags =
		ALL_LOWERS ALL_UPPERS ","


@@ 390,7 293,10 @@ test_mode_cfg_subtypes(void)

	/* Test valid string */
	assert_eq(mode_cfg_subtypes(&cfg, all_flags), MODE_ERR_NONE);
	CHECK(ALL_LOWERS ALL_UPPERS, "", "", "");
	CHECK(ALL_LOWERS ALL_UPPERS,
	      ALL_LOWERS ALL_UPPERS,
	      ALL_LOWERS ALL_UPPERS,
	      ALL_LOWERS ALL_UPPERS);

#undef CHECK
}


@@ 450,38 356,7 @@ test_mode_cfg_prefix(void)
}

static void
test_mode_cfg_modes(void)
{
	/* Test configuring MODES */

	struct mode_cfg cfg = {
		.MODES = 3
	};

	/* Test empty string */
	assert_eq(mode_cfg_modes(&cfg, ""), MODE_ERR_INVALID_CONFIG);
	assert_eq(cfg.MODES, 3);

	/* Test not a number */
	assert_eq(mode_cfg_modes(&cfg, "1abc"), MODE_ERR_INVALID_CONFIG);
	assert_eq(mode_cfg_modes(&cfg, "wxyz"), MODE_ERR_INVALID_CONFIG);
	assert_eq(cfg.MODES, 3);

	/* Test invalid number (i.e.: not [1-99]) */
	assert_eq(mode_cfg_modes(&cfg, "0"),   MODE_ERR_INVALID_CONFIG);
	assert_eq(mode_cfg_modes(&cfg, "100"), MODE_ERR_INVALID_CONFIG);
	assert_eq(cfg.MODES, 3);

	/* Teset valid numbers */
	assert_eq(mode_cfg_modes(&cfg, "1"),  MODE_ERR_NONE);
	assert_eq(cfg.MODES, 1);

	assert_eq(mode_cfg_modes(&cfg, "99"), MODE_ERR_NONE);
	assert_eq(cfg.MODES, 99);
}

static void
test_chanmode_type(void)
test_mode_type(void)
{
	/* Test retrieving a mode flag type */



@@ 497,53 372,48 @@ test_chanmode_type(void)
	if (config_errs != MODE_ERR_NONE)
		test_abort("Configuration error");

	/* Test invalid '+'/'-' */
	assert_eq(chanmode_type(&cfg, MODE_SET_INVALID, 'a'), MODE_FLAG_INVALID_SET);

	/* Test invalid flag */
	assert_eq(chanmode_type(&cfg, MODE_SET_ON, '!'), MODE_FLAG_INVALID_FLAG);
	assert_eq(mode_type(&cfg, '!', 1), MODE_FLAG_INVALID_FLAG);

	/* Test flag not in usermodes, chanmodes */
	assert_eq(chanmode_type(&cfg, MODE_SET_ON, 'z'), MODE_FLAG_INVALID_FLAG);
	assert_eq(mode_type(&cfg, 'z', 1), MODE_FLAG_INVALID_FLAG);

	/* Test chanmode A (always has a parameter) */
	assert_eq(chanmode_type(&cfg, MODE_SET_ON,  'b'), MODE_FLAG_CHANMODE_PARAM);
	assert_eq(chanmode_type(&cfg, MODE_SET_OFF, 'b'), MODE_FLAG_CHANMODE_PARAM);
	assert_eq(mode_type(&cfg, 'b', 1), MODE_FLAG_CHANMODE_PARAM);
	assert_eq(mode_type(&cfg, 'b', 0), MODE_FLAG_CHANMODE_PARAM);

	/* Test chanmode B (always has a parameter) */
	assert_eq(chanmode_type(&cfg, MODE_SET_ON,  'c'), MODE_FLAG_CHANMODE_PARAM);
	assert_eq(chanmode_type(&cfg, MODE_SET_OFF, 'c'), MODE_FLAG_CHANMODE_PARAM);
	assert_eq(mode_type(&cfg, 'c', 1), MODE_FLAG_CHANMODE_PARAM);
	assert_eq(mode_type(&cfg, 'c', 0), MODE_FLAG_CHANMODE_PARAM);

	/* Test chanmode C (only has a parameter when set) */
	assert_eq(chanmode_type(&cfg, MODE_SET_ON,  'd'), MODE_FLAG_CHANMODE_PARAM);
	assert_eq(chanmode_type(&cfg, MODE_SET_OFF, 'd'), MODE_FLAG_CHANMODE);
	assert_eq(mode_type(&cfg, 'd', 1), MODE_FLAG_CHANMODE_PARAM);
	assert_eq(mode_type(&cfg, 'd', 0), MODE_FLAG_CHANMODE);

	/* Test chanmode D (never has a parameter) */
	assert_eq(chanmode_type(&cfg, MODE_SET_ON,  'e'), MODE_FLAG_CHANMODE);
	assert_eq(chanmode_type(&cfg, MODE_SET_OFF, 'e'), MODE_FLAG_CHANMODE);
	assert_eq(mode_type(&cfg, 'e', 1), MODE_FLAG_CHANMODE);
	assert_eq(mode_type(&cfg, 'e', 0), MODE_FLAG_CHANMODE);

	/* Test prefix flag */
	assert_eq(chanmode_type(&cfg, MODE_SET_ON,  'f'), MODE_FLAG_PREFIX);
	assert_eq(chanmode_type(&cfg, MODE_SET_OFF, 'f'), MODE_FLAG_PREFIX);
	assert_eq(mode_type(&cfg, 'f', 1), MODE_FLAG_PREFIX);
	assert_eq(mode_type(&cfg, 'f', 0), MODE_FLAG_PREFIX);
}

int
main(void)
{
	struct testcase tests[] = {
		TESTCASE(test_flag_bit),
		TESTCASE(test_mode_bit),
		TESTCASE(test_mode_str),
		TESTCASE(test_chanmode_set),
		TESTCASE(test_prfxmode_set),
		TESTCASE(test_usermode_set),
		TESTCASE(test_chanmode_prefix),
		TESTCASE(test_prfxmode_prefix),
		TESTCASE(test_mode_chanmode_set),
		TESTCASE(test_mode_prfxmode_prefix),
		TESTCASE(test_mode_prfxmode_set),
		TESTCASE(test_mode_usermode_set),
		TESTCASE(test_mode_cfg_usermodes),
		TESTCASE(test_mode_cfg_chanmodes),
		TESTCASE(test_mode_cfg_subtypes),
		TESTCASE(test_mode_cfg_prefix),
		TESTCASE(test_mode_cfg_modes),
		TESTCASE(test_chanmode_type)
		TESTCASE(test_mode_type)
	};

	return run_tests(NULL, NULL, tests);

M test/handlers/irc_recv.c => test/handlers/irc_recv.c +7 -7
@@ 228,7 228,7 @@ test_irc_numeric_353(void)
	assert_strcmp(mock_line[0], "RPL_NAMEREPLY: channel '#x' not found");

	CHECK_RECV("353 me x #c1 :n1", 1, 1, 0);
	assert_strcmp(mock_line[0], "RPL_NAMEREPLY: invalid channel flag: 'x'");
	assert_strcmp(mock_line[0], "RPL_NAMEREPLY: invalid channel type: 'x'");

	CHECK_RECV("353 me = #c1 :+@", 1, 1, 0);
	assert_strcmp(mock_line[0], "RPL_NAMEREPLY: invalid nick: '+@'");


@@ 255,8 255,8 @@ test_irc_numeric_353(void)
	 || !(u3 = user_list_get(&(c1->users), CASEMAPPING_RFC1459, "n3", 0)))
		test_abort("Failed to retrieve users");

	assert_eq(u1->prfxmodes.lower, (flag_bit('o')));
	assert_eq(u2->prfxmodes.lower, (flag_bit('v')));
	assert_eq(u1->prfxmodes.lower, (mode_bit('o')));
	assert_eq(u2->prfxmodes.lower, (mode_bit('v')));
	assert_eq(u3->prfxmodes.lower, 0);

	/* test multiple nicks, multiprefix enabled */


@@ 275,10 275,10 @@ test_irc_numeric_353(void)
	assert_eq(u2->prfxmodes.prefix, '+');
	assert_eq(u3->prfxmodes.prefix, '@');
	assert_eq(u4->prfxmodes.prefix, '@');
	assert_eq(u1->prfxmodes.lower, (flag_bit('o')));
	assert_eq(u2->prfxmodes.lower, (flag_bit('v')));
	assert_eq(u3->prfxmodes.lower, (flag_bit('o') | flag_bit('v')));
	assert_eq(u4->prfxmodes.lower, (flag_bit('o') | flag_bit('v')));
	assert_eq(u1->prfxmodes.lower, (mode_bit('o')));
	assert_eq(u2->prfxmodes.lower, (mode_bit('v')));
	assert_eq(u3->prfxmodes.lower, (mode_bit('o') | mode_bit('v')));
	assert_eq(u4->prfxmodes.lower, (mode_bit('o') | mode_bit('v')));
}

static void