~rcr/rirc

8ff6e7ebc6a1fad9a4346e1e63140b189b325af9 — Richard Robbins 1 year, 5 months ago 49ffbe2
add ircv3 CAP NEW/DEL
M README.md => README.md +1 -1
@@ 42,7 42,7 @@ See: https://www.gnu.org/software/gperf/

Initialize, configure and build mbedtls:

```bash
```
git submodule init
git submodule update --recursive
cd mbedtls

A src/components/ircv3.c => src/components/ircv3.c +40 -0
@@ 0,0 1,40 @@
#include "src/components/ircv3.h"

#include <string.h>

struct ircv3_cap*
ircv3_cap_get(struct ircv3_caps *caps, const char *cap_str)
{
	#define X(CAP, VAR, ATTRS) \
	if (!strcmp(cap_str, CAP)) \
		return &(caps->VAR);
	IRCV3_CAPS
	#undef X

	return NULL;
}

void
ircv3_caps(struct ircv3_caps *caps)
{
	#define X(CAP, VAR, ATTRS) \
	caps->VAR.req = 0;                                    \
	caps->VAR.set = 0;                                    \
	caps->VAR.supported = 0;                              \
	caps->VAR.supports_del = !(ATTRS & IRCV3_CAP_NO_DEL); \
	caps->VAR.supports_req = !(ATTRS & IRCV3_CAP_NO_REQ); \
	caps->VAR.req_auto = (ATTRS & IRCV3_CAP_AUTO);
	IRCV3_CAPS
	#undef X
}

void
ircv3_caps_reset(struct ircv3_caps *caps)
{
	#define X(CAP, VAR, ATTRS) \
	caps->VAR.req = 0; \
	caps->VAR.set = 0; \
	caps->VAR.supported = 0;
	IRCV3_CAPS
	#undef X
}

R src/components/ircv3_cap.h => src/components/ircv3.h +11 -10
@@ 1,7 1,9 @@
#ifndef IRCV3_CAP_H
#define IRCV3_CAP_H

#define IRCV3_CAP_AUTO    1
#define IRCV3_CAP_AUTO   (1 << 0)
#define IRCV3_CAP_NO_DEL (1 << 1)
#define IRCV3_CAP_NO_REQ (1 << 2)
#define IRCV3_CAP_VERSION "302"

#ifndef IRCV3_CAPS


@@ 11,9 13,12 @@

struct ircv3_cap
{
	unsigned req      : 1; /* cap REQ sent */
	unsigned req_auto : 1; /* cap REQ sent during registration */
	unsigned set      : 1; /* cap is unset/set */
	unsigned req          : 1; /* cap REQ sent */
	unsigned req_auto     : 1; /* cap REQ sent during registration */
	unsigned set          : 1; /* cap is unset/set */
	unsigned supported    : 1; /* cap is supported by server */
	unsigned supports_del : 1; /* cap supports CAP DEL */
	unsigned supports_req : 1; /* cap supports CAP REQ */
};

struct ircv3_caps


@@ 22,15 27,11 @@ struct ircv3_caps
	struct ircv3_cap VAR;
	IRCV3_CAPS
	#undef X
	unsigned cap_reqs; /* number of CAP REQs awaiting response */
};

struct ircv3_cap* ircv3_cap_get(struct ircv3_caps*, const char*);

void ircv3_caps(struct ircv3_caps*);
void ircv3_caps_reset(struct ircv3_caps*);

int ircv3_cap_ack(struct ircv3_caps*, const char*);
int ircv3_cap_nak(struct ircv3_caps*, const char*);

const char *ircv3_cap_err(int);

#endif

D src/components/ircv3_cap.c => src/components/ircv3_cap.c +0 -123
@@ 1,123 0,0 @@
#include "src/components/ircv3_cap.h"

#include <string.h>

enum ircv3_cap_err
{
	IRCV3_CAP_ERR_NONE,
	IRCV3_CAP_ERR_NO_REQ,
	IRCV3_CAP_ERR_UNSUPPORTED,
	IRCV3_CAP_ERR_WAS_SET,
	IRCV3_CAP_ERR_WAS_UNSET,
};

static struct ircv3_cap* ircv3_cap_get(struct ircv3_caps*, const char*);

void
ircv3_caps(struct ircv3_caps *caps)
{
	#define X(CAP, VAR, ATTRS) \
	caps->VAR.req = 0; \
	caps->VAR.set = 0; \
	caps->VAR.req_auto = (ATTRS & IRCV3_CAP_AUTO);
	IRCV3_CAPS
	#undef X

	caps->cap_reqs = 0;
}

void
ircv3_caps_reset(struct ircv3_caps *caps)
{
	#define X(CAP, VAR, ATTRS) \
	caps->VAR.req = 0; \
	caps->VAR.set = 0;
	IRCV3_CAPS
	#undef X

	caps->cap_reqs = 0;
}

int
ircv3_cap_ack(struct ircv3_caps *caps, const char *cap_str)
{
	int unset;
	struct ircv3_cap *cap;

	if ((unset = (*cap_str == '-')))
		cap_str++;

	if (!(cap = ircv3_cap_get(caps, cap_str)))
		return IRCV3_CAP_ERR_UNSUPPORTED;

	if (!cap->req)
		return IRCV3_CAP_ERR_NO_REQ;

	cap->req = 0;

	if (!unset && cap->set)
		return IRCV3_CAP_ERR_WAS_SET;

	if (unset && !cap->set)
		return IRCV3_CAP_ERR_WAS_UNSET;

	cap->set = !unset;

	if (caps->cap_reqs)
		caps->cap_reqs--;

	return IRCV3_CAP_ERR_NONE;
}

int
ircv3_cap_nak(struct ircv3_caps *caps, const char *cap_str)
{
	int unset;
	struct ircv3_cap *cap;

	if ((unset = (*cap_str == '-')))
		cap_str++;

	if (!(cap = ircv3_cap_get(caps, cap_str)))
		return IRCV3_CAP_ERR_UNSUPPORTED;

	if (!cap->req)
		return IRCV3_CAP_ERR_NO_REQ;

	cap->req = 0;

	if (!unset && cap->set)
		return IRCV3_CAP_ERR_WAS_SET;

	if (unset && !cap->set)
		return IRCV3_CAP_ERR_WAS_UNSET;

	if (caps->cap_reqs)
		caps->cap_reqs--;

	return IRCV3_CAP_ERR_NONE;
}

const char*
ircv3_cap_err(int err)
{
	switch (err) {
		case IRCV3_CAP_ERR_NO_REQ:      return "no cap REQ";
		case IRCV3_CAP_ERR_UNSUPPORTED: return "not supported";
		case IRCV3_CAP_ERR_WAS_SET:     return "was set";
		case IRCV3_CAP_ERR_WAS_UNSET:   return "was unset";
		default:                        return "unknown error";
	}
}

static struct ircv3_cap*
ircv3_cap_get(struct ircv3_caps *caps, const char *cap_str)
{
	#define X(CAP, VAR, ATTRS) \
	if (!strcmp(cap_str, CAP)) \
		return &(caps->VAR);
	IRCV3_CAPS
	#undef X

	return NULL;
}

M src/components/server.c => src/components/server.c +0 -2
@@ 3,8 3,6 @@
#include <stdlib.h>
#include <string.h>

#include "src/components/ircv3_cap.h"

#include "src/components/server.h"
#include "src/state.h"
#include "src/utils/utils.h"

M src/components/server.h => src/components/server.h +1 -2
@@ 3,9 3,8 @@

#include "src/components/buffer.h"
#include "src/components/channel.h"
#include "src/components/ircv3_cap.h"
#include "src/components/ircv3.h"
#include "src/components/mode.h"
#include "src/utils/utils.h"

// TODO: move this to utils
#define IRC_MESSAGE_LEN 510

M src/handlers/irc_recv.c => src/handlers/irc_recv.c +1 -0
@@ 167,6 167,7 @@ static const irc_recv_f irc_numerics[] = {
	[407] = irc_error,  /* ERR_TOOMANYTARGETS */
	[408] = irc_error,  /* ERR_NOSUCHSERVICE */
	[409] = irc_error,  /* ERR_NOORIGIN */
	[410] = irc_error,  /* ERR_INVALIDCAPCMD */
	[411] = irc_error,  /* ERR_NORECIPIENT */
	[412] = irc_error,  /* ERR_NOTEXTTOSEND */
	[413] = irc_error,  /* ERR_NOTOPLEVEL */

M src/handlers/irc_send.c => src/handlers/irc_send.c +1 -1
@@ 4,7 4,7 @@
#include "config.h"
#include "src/components/buffer.h"
#include "src/components/channel.h"
#include "src/components/ircv3_cap.h"
#include "src/components/ircv3.h"
#include "src/components/server.h"
#include "src/handlers/irc_send.gperf.out"
#include "src/handlers/irc_send.h"

M src/handlers/ircv3.c => src/handlers/ircv3.c +120 -41
@@ 28,6 28,8 @@ static int ircv3_recv_cap_##CMD(struct server*, struct irc_message*);
IRCV3_RECV_HANDLERS
#undef X

static int ircv3_cap_req_count(struct ircv3_caps*);

int
ircv3_recv_CAP(struct server *s, struct irc_message *m)
{


@@ 91,29 93,26 @@ ircv3_recv_cap_LS(struct server *s, struct irc_message *m)
	}

	while ((cap = strsep(&(caps)))) {
		#define X(CAP, VAR, ATTRS)                             \
		if (!strcmp(cap, CAP) && s->ircv3_caps.VAR.req_auto) { \
			s->ircv3_caps.VAR.req = 1;                         \
			s->ircv3_caps.cap_reqs++;                          \
		}
		IRCV3_CAPS
		#undef X

		struct ircv3_cap *c;

		if ((c = ircv3_cap_get(&(s->ircv3_caps), cap)))
			c->supported = 1;
	}

	if (multiline)
		return 0;

	if (s->ircv3_caps.cap_reqs) {
		#define X(CAP, VAR, ATTRS)     \
		if (s->ircv3_caps.VAR.req) {   \
			s->ircv3_caps.VAR.req = 0; \
			sendf(s, "CAP REQ :" CAP); \
		}
		IRCV3_CAPS
		#undef X
	} else {
		sendf(s, "CAP END");
	#define X(CAP, VAR, ATTRS)     \
	if (s->ircv3_caps.VAR.supported && s->ircv3_caps.VAR.req_auto) { \
		s->ircv3_caps.VAR.req = 1; \
		sendf(s, "CAP REQ :" CAP); \
	}
	IRCV3_CAPS
	#undef X

	if (!ircv3_cap_req_count(&(s->ircv3_caps)))
		sendf(s, "CAP END");

	return 0;
}


@@ 175,7 174,6 @@ ircv3_recv_cap_ACK(struct server *s, struct irc_message *m)

	char *cap;
	char *caps;
	int err;
	int errors = 0;

	if (!irc_message_param(m, &caps))


@@ 185,18 183,47 @@ ircv3_recv_cap_ACK(struct server *s, struct irc_message *m)
		failf(s, "CAP ACK: parameter is empty");

	do {
		if ((err = ircv3_cap_ack(&(s->ircv3_caps), cap))) {
		int unset;
		struct ircv3_cap *c;

		if ((unset = (*cap == '-')))
			cap++;

		if (!(c = ircv3_cap_get(&(s->ircv3_caps), cap))) {
			server_error(s, "CAP ACK: '%s' not supported", cap);
			errors++;
			continue;
		}

		if (!c->req) {
			server_error(s, "CAP ACK: '%s%s' was not requested", (unset ? "-" : ""), cap);
			errors++;
			continue;
		}

		if (!unset && c->set) {
			server_error(s, "CAP ACK: '%s' was set", cap);
			errors++;
			server_info(s, "capability change accepted: %s (error: %s)", cap, ircv3_cap_err(err));
		} else {
			server_info(s, "capability change accepted: %s", cap);
			continue;
		}

		if (unset && !c->set) {
			server_error(s, "CAP ACK: '%s' was not set", cap);
			errors++;
			continue;
		}

		c->req = 0;
		c->set = !unset;

		server_info(s, "capability change accepted: %s%s", (unset ? "-" : ""), cap);

	} while ((cap = strsep(&(caps))));

	if (errors)
		failf(s, "CAP ACK: parameter errors");

	if (!s->registered && !s->ircv3_caps.cap_reqs)
	if (!s->registered && !ircv3_cap_req_count(&(s->ircv3_caps)))
		sendf(s, "CAP END");

	return 0;


@@ 213,8 240,6 @@ ircv3_recv_cap_NAK(struct server *s, struct irc_message *m)

	char *cap;
	char *caps;
	int err;
	int errors = 0;

	if (!irc_message_param(m, &caps))
		failf(s, "CAP NAK: parameter is null");


@@ 223,18 248,16 @@ ircv3_recv_cap_NAK(struct server *s, struct irc_message *m)
		failf(s, "CAP NAK: parameter is empty");

	do {
		if ((err = ircv3_cap_nak(&(s->ircv3_caps), cap))) {
			errors++;
			server_info(s, "capability change rejected: %s (error: %s)", cap, ircv3_cap_err(err));
		} else {
			server_info(s, "capability change rejected: %s", cap);
		}
	} while ((cap = strsep(&(caps))));
		struct ircv3_cap *c;

	if (errors)
		failf(s, "CAP NAK: parameter errors");
		if ((c = ircv3_cap_get(&(s->ircv3_caps), cap)))
			c->req = 0;

		server_info(s, "capability change rejected: %s", cap);

	if (!s->registered && !s->ircv3_caps.cap_reqs)
	} while ((cap = strsep(&(caps))));

	if (!s->registered && !ircv3_cap_req_count(&(s->ircv3_caps)))
		sendf(s, "CAP END");

	return 0;


@@ 252,10 275,32 @@ ircv3_recv_cap_DEL(struct server *s, struct irc_message *m)
	 * CAP <targ> DEL :<cap_1> [<cap_2> [...]]
	 */

	/* TODO */
	char *cap;
	char *caps;

	if (!irc_message_param(m, &caps))
		failf(s, "CAP DEL: parameter is null");

	if (!(cap = strsep(&(caps))))
		failf(s, "CAP DEL: parameter is empty");

	do {
		struct ircv3_cap *c;

		if (!(c = ircv3_cap_get(&(s->ircv3_caps), cap)))
			continue;

		if (!c->supports_del)
			failf(s, "CAP DEL: '%s' doesn't support DEL", cap);

		c->req = 0;
		c->set = 0;
		c->supported = 0;

		server_info(s, "capability lost: %s", cap);

	} while ((cap = strsep(&(caps))));

	(void)s;
	(void)m;
	return 0;
}



@@ 269,9 314,43 @@ ircv3_recv_cap_NEW(struct server *s, struct irc_message *m)
	 * CAP <targ> NEW :<cap_1> [<cap_2> [...]]
	 */

	/* TODO */
	char *cap;
	char *caps;

	if (!irc_message_param(m, &caps))
		failf(s, "CAP NEW: parameter is null");

	if (!(cap = strsep(&(caps))))
		failf(s, "CAP NEW: parameter is empty");

	do {
		struct ircv3_cap *c;

		if (!(c = ircv3_cap_get(&(s->ircv3_caps), cap)))
			continue;

		c->supported = 1;

		if (!c->set && !c->req && c->req_auto) {
			c->req = 1;
			sendf(s, "CAP REQ :%s", cap);
		}

		server_info(s, "new capability: %s", cap);

	} while ((cap = strsep(&(caps))));

	(void)s;
	(void)m;
	return 0;
}

static int
ircv3_cap_req_count(struct ircv3_caps *caps)
{
	int ret = 0;
	#define X(CAP, VAR, ATTRS) \
	if (caps->VAR.req) \
		ret++;
	IRCV3_CAPS
	#undef X
	return ret;
}

M src/handlers/ircv3.h => src/handlers/ircv3.h +0 -6
@@ 1,25 1,19 @@
/* TODO
 *  - cap commands (after registration)
 *      - /cap-req [-]cap [...]
 *  - cap NEW/DEL
 *  - cap setting for auto on connect/NEW
 *      - :set, set/unset auto, and for printing state
 *  - 410 ERR_INVALIDCAPCMD
 *  - cap-notify handling
 *  - list caps with `(unsupported)` when printing LS results?
 *  - LS caps:
 *      [name] [set/unset] [auto]
 *      [name] [unsupported]
 *  - LIST caps:
 *      [name] [auto]
 *  - disconnect on CAP protocol errors during registration
 *  - CAP args, e.g. cap=foo,bar
 */

#ifndef IRCV3_H
#define IRCV3_H

#include "src/components/ircv3_cap.h"
#include "src/components/server.h"
#include "src/utils/utils.h"


A test/components/ircv3.c => test/components/ircv3.c +68 -0
@@ 0,0 1,68 @@
#include "test/test.h"

#define IRCV3_CAPS \
	X("cap-1", cap_1, IRCV3_CAP_AUTO) \
	X("cap-2", cap_2, 0) \
	X("cap-3", cap_3, (IRCV3_CAP_NO_DEL | IRCV3_CAP_NO_REQ))

#include "src/components/ircv3.c"

static void
test_ircv3_caps(void)
{
	struct ircv3_caps caps;

	ircv3_caps(&caps);

	assert_eq(caps.cap_1.req, 0);
	assert_eq(caps.cap_2.req, 0);
	assert_eq(caps.cap_3.req, 0);
	assert_eq(caps.cap_1.req_auto, 1);
	assert_eq(caps.cap_2.req_auto, 0);
	assert_eq(caps.cap_3.req_auto, 0);
	assert_eq(caps.cap_1.set, 0);
	assert_eq(caps.cap_2.set, 0);
	assert_eq(caps.cap_3.set, 0);
	assert_eq(caps.cap_1.supported, 0);
	assert_eq(caps.cap_2.supported, 0);
	assert_eq(caps.cap_3.supported, 0);
	assert_eq(caps.cap_1.supports_del, 1);
	assert_eq(caps.cap_2.supports_del, 1);
	assert_eq(caps.cap_3.supports_del, 0);
	assert_eq(caps.cap_1.supports_req, 1);
	assert_eq(caps.cap_2.supports_req, 1);
	assert_eq(caps.cap_3.supports_req, 0);
}

static void
test_ircv3_caps_reset(void)
{
	struct ircv3_caps caps;

	caps.cap_3.req = 1;
	caps.cap_3.req_auto = 1;
	caps.cap_3.set = 1;
	caps.cap_3.supported = 1;
	caps.cap_3.supports_del = 1;
	caps.cap_3.supports_req = 1;

	ircv3_caps_reset(&caps);

	assert_eq(caps.cap_3.req, 0);
	assert_eq(caps.cap_3.req_auto, 1);
	assert_eq(caps.cap_3.set, 0);
	assert_eq(caps.cap_3.supported, 0);
	assert_eq(caps.cap_3.supports_del, 1);
	assert_eq(caps.cap_3.supports_req, 1);
}

int
main(void)
{
	struct testcase tests[] = {
		TESTCASE(test_ircv3_caps),
		TESTCASE(test_ircv3_caps_reset),
	};

	return run_tests(tests);
}

D test/components/ircv3_cap.c => test/components/ircv3_cap.c +0 -132
@@ 1,132 0,0 @@
#include "test/test.h"

#define IRCV3_CAPS \
	X("cap-1", cap_1, IRCV3_CAP_AUTO) \
	X("cap-2", cap_2, 0)

#include "src/components/ircv3_cap.c"

static void
test_ircv3_caps(void)
{
	struct ircv3_caps caps;

	ircv3_caps(&caps);

	assert_eq(caps.cap_1.req, 0);
	assert_eq(caps.cap_2.req, 0);
	assert_eq(caps.cap_1.req_auto, 1);
	assert_eq(caps.cap_2.req_auto, 0);
	assert_eq(caps.cap_1.set, 0);
	assert_eq(caps.cap_2.set, 0);
}

static void
test_ircv3_caps_reset(void)
{
	/* TODO */
}

static void
test_ircv3_cap_ack(void)
{
	struct ircv3_caps caps;

	ircv3_caps(&caps);

	caps.cap_reqs = 2;

	assert_eq(caps.cap_1.set, 0);
	assert_eq(caps.cap_1.req, 0);

	caps.cap_1.req = 1;
	caps.cap_1.set = 0;
	assert_eq(ircv3_cap_ack(&caps, "cap-1"), 0);
	assert_eq(caps.cap_1.set, 1);
	assert_eq(caps.cap_1.req, 0);
	assert_eq(caps.cap_reqs, 1);

	caps.cap_1.req = 1;
	caps.cap_1.set = 1;
	assert_eq(ircv3_cap_ack(&caps, "-cap-1"), 0);
	assert_eq(caps.cap_1.set, 0);
	assert_eq(caps.cap_1.req, 0);
	assert_eq(caps.cap_reqs, 0);

	assert_eq(ircv3_cap_ack(&caps, "cap-unsupported"), IRCV3_CAP_ERR_UNSUPPORTED);

	caps.cap_1.req = 0;
	caps.cap_1.set = 0;
	assert_eq(ircv3_cap_ack(&caps, "cap-1"), IRCV3_CAP_ERR_NO_REQ);
	caps.cap_1.set = 0;

	caps.cap_1.req = 1;
	caps.cap_1.set = 1;
	assert_eq(ircv3_cap_ack(&caps, "cap-1"), IRCV3_CAP_ERR_WAS_SET);

	caps.cap_1.req = 1;
	caps.cap_1.set = 0;
	assert_eq(ircv3_cap_ack(&caps, "-cap-1"), IRCV3_CAP_ERR_WAS_UNSET);
}

static void
test_ircv3_cap_nak(void)
{
	struct ircv3_caps caps;

	ircv3_caps(&caps);

	caps.cap_reqs = 2;

	assert_eq(caps.cap_1.set, 0);
	assert_eq(caps.cap_1.req, 0);

	caps.cap_1.req = 1;
	caps.cap_1.set = 0;
	assert_eq(ircv3_cap_nak(&caps, "cap-1"), 0);
	assert_eq(caps.cap_1.set, 0);
	assert_eq(caps.cap_1.req, 0);
	assert_eq(caps.cap_reqs, 1);

	caps.cap_1.req = 1;
	caps.cap_1.set = 1;
	assert_eq(ircv3_cap_nak(&caps, "-cap-1"), 0);
	assert_eq(caps.cap_1.set, 1);
	assert_eq(caps.cap_1.req, 0);
	assert_eq(caps.cap_reqs, 0);

	assert_eq(ircv3_cap_nak(&caps, "cap-unsupported"), IRCV3_CAP_ERR_UNSUPPORTED);

	caps.cap_1.req = 0;
	caps.cap_1.set = 0;
	assert_eq(ircv3_cap_nak(&caps, "cap-1"), IRCV3_CAP_ERR_NO_REQ);
	caps.cap_1.set = 0;

	caps.cap_1.req = 1;
	caps.cap_1.set = 1;
	assert_eq(ircv3_cap_nak(&caps, "cap-1"), IRCV3_CAP_ERR_WAS_SET);

	caps.cap_1.req = 1;
	caps.cap_1.set = 0;
	assert_eq(ircv3_cap_nak(&caps, "-cap-1"), IRCV3_CAP_ERR_WAS_UNSET);
}

int
main(void)
{
	struct testcase tests[] = {
		TESTCASE(test_ircv3_caps),
		TESTCASE(test_ircv3_caps_reset),
		TESTCASE(test_ircv3_cap_ack),
		TESTCASE(test_ircv3_cap_nak),
	};

	/* Coverage */
	(void) ircv3_cap_err(IRCV3_CAP_ERR_NO_REQ);
	(void) ircv3_cap_err(IRCV3_CAP_ERR_UNSUPPORTED);
	(void) ircv3_cap_err(IRCV3_CAP_ERR_WAS_SET);
	(void) ircv3_cap_err(IRCV3_CAP_ERR_WAS_UNSET);
	(void) ircv3_cap_err(-1);

	return run_tests(tests);
}

M test/components/server.c => test/components/server.c +1 -1
@@ 2,7 2,7 @@
#include "src/components/buffer.c"
#include "src/components/channel.c"
#include "src/components/input.c"
#include "src/components/ircv3_cap.c"
#include "src/components/ircv3.c"
#include "src/components/mode.c"
#include "src/components/server.c"
#include "src/components/user.c"

M test/draw.c => test/draw.c +1 -1
@@ 3,7 3,7 @@
#include "src/components/buffer.c"
#include "src/components/channel.c"
#include "src/components/input.c"
#include "src/components/ircv3_cap.c"
#include "src/components/ircv3.c"
#include "src/components/mode.c"
#include "src/components/server.c"
#include "src/components/user.c"

M test/handlers/irc_ctcp.c => test/handlers/irc_ctcp.c +1 -1
@@ 3,7 3,7 @@
#include "src/components/buffer.c"
#include "src/components/channel.c"
#include "src/components/input.c"
#include "src/components/ircv3_cap.c"
#include "src/components/ircv3.c"
#include "src/components/mode.c"
#include "src/components/server.c"
#include "src/components/user.c"

M test/handlers/irc_recv.c => test/handlers/irc_recv.c +1 -1
@@ 3,7 3,7 @@
#include "src/components/buffer.c"
#include "src/components/channel.c"
#include "src/components/input.c"
#include "src/components/ircv3_cap.c"
#include "src/components/ircv3.c"
#include "src/components/mode.c"
#include "src/components/server.c"
#include "src/components/user.c"

M test/handlers/irc_send.c => test/handlers/irc_send.c +1 -1
@@ 3,7 3,7 @@
#include "src/components/buffer.c"
#include "src/components/channel.c"
#include "src/components/input.c"
#include "src/components/ircv3_cap.c"
#include "src/components/ircv3.c"
#include "src/components/mode.c"
#include "src/components/server.c"
#include "src/components/user.c"

M test/handlers/ircv3.c => test/handlers/ircv3.c +197 -54
@@ 6,12 6,12 @@
	X("cap-2", cap_2, 0) \
	X("cap-3", cap_3, IRCV3_CAP_AUTO) \
	X("cap-4", cap_4, IRCV3_CAP_AUTO) \
	X("cap-5", cap_5, IRCV3_CAP_AUTO)
	X("cap-5", cap_5, (IRCV3_CAP_AUTO | IRCV3_CAP_NO_DEL))

#include "src/components/buffer.c"
#include "src/components/channel.c"
#include "src/components/input.c"
#include "src/components/ircv3_cap.c"
#include "src/components/ircv3.c"
#include "src/components/mode.c"
#include "src/components/server.c"
#include "src/components/user.c"


@@ 227,29 227,41 @@ test_ircv3_CAP_LS(void)

	/* test with leading ':' */
	test_buf_reset();
	ircv3_caps_reset(&(s->ircv3_caps));
	IRC_MESSAGE_PARSE("CAP * LS :cap-1");
	assert_eq(irc_recv(s, &m), 0);
	assert_eq(send_buf_n, 1);
	assert_strcmp(send_buf[0], "CAP REQ :cap-1");
	assert_eq(s->ircv3_caps.cap_1.supported, 1);
	assert_eq(s->ircv3_caps.cap_2.supported, 0);

	/* test multiple caps, cap_2 is non-auto */
	test_buf_reset();
	ircv3_caps_reset(&(s->ircv3_caps));
	IRC_MESSAGE_PARSE("CAP * LS :cap-1 cap-2 cap-3");
	assert_eq(irc_recv(s, &m), 0);
	assert_eq(send_buf_n, 2);
	assert_strcmp(send_buf[0], "CAP REQ :cap-1");
	assert_strcmp(send_buf[1], "CAP REQ :cap-3");
	assert_eq(s->ircv3_caps.cap_1.supported, 1);
	assert_eq(s->ircv3_caps.cap_2.supported, 1);
	assert_eq(s->ircv3_caps.cap_3.supported, 1);

	/* test multiple caps, with unsupported */
	test_buf_reset();
	ircv3_caps_reset(&(s->ircv3_caps));
	IRC_MESSAGE_PARSE("CAP * LS :cap-1 foo cap-2 bar cap-3");
	assert_eq(irc_recv(s, &m), 0);
	assert_eq(send_buf_n, 2);
	assert_strcmp(send_buf[0], "CAP REQ :cap-1");
	assert_strcmp(send_buf[1], "CAP REQ :cap-3");
	assert_eq(s->ircv3_caps.cap_1.supported, 1);
	assert_eq(s->ircv3_caps.cap_2.supported, 1);
	assert_eq(s->ircv3_caps.cap_3.supported, 1);

	/* test multiline */
	test_buf_reset();
	ircv3_caps_reset(&(s->ircv3_caps));
	IRC_MESSAGE_PARSE("CAP * LS * cap-1");
	assert_eq(irc_recv(s, &m), 0);
	assert_eq(send_buf_n, 0);


@@ 262,6 274,10 @@ test_ircv3_CAP_LS(void)
	assert_strcmp(send_buf[0], "CAP REQ :cap-1");
	assert_strcmp(send_buf[1], "CAP REQ :cap-3");
	assert_strcmp(send_buf[2], "CAP REQ :cap-4");
	assert_eq(s->ircv3_caps.cap_1.supported, 1);
	assert_eq(s->ircv3_caps.cap_2.supported, 1);
	assert_eq(s->ircv3_caps.cap_3.supported, 1);
	assert_eq(s->ircv3_caps.cap_4.supported, 1);

	/* test registered */
	test_buf_reset();


@@ 388,9 404,9 @@ test_ircv3_CAP_ACK(void)
	assert_eq(send_buf_n, 0);
	assert_eq(line_buf_n, 5);
	assert_strcmp(line_buf[0], "capability change accepted: cap-1");
	assert_strcmp(line_buf[1], "capability change accepted: cap-aaa (error: not supported)");
	assert_strcmp(line_buf[1], "CAP ACK: 'cap-aaa' not supported");
	assert_strcmp(line_buf[2], "capability change accepted: cap-2");
	assert_strcmp(line_buf[3], "capability change accepted: cap-bbb (error: not supported)");
	assert_strcmp(line_buf[3], "CAP ACK: 'cap-bbb' not supported");
	assert_strcmp(line_buf[4], "CAP ACK: parameter errors");

	/* unregistered, no error */


@@ 398,33 414,44 @@ test_ircv3_CAP_ACK(void)
	s->registered = 0;
	s->ircv3_caps.cap_1.set = 0;
	s->ircv3_caps.cap_2.set = 1;
	s->ircv3_caps.cap_3.set = 1;
	s->ircv3_caps.cap_1.req = 1;
	s->ircv3_caps.cap_2.req = 1;
	s->ircv3_caps.cap_3.req = 1;
	IRC_MESSAGE_PARSE("CAP * ACK :cap-1 -cap-2");
	assert_eq(irc_recv(s, &m), 0);
	assert_eq(send_buf_n, 1);
	assert_eq(send_buf_n, 0);
	assert_eq(line_buf_n, 2);
	assert_strcmp(line_buf[0], "capability change accepted: cap-1");
	assert_strcmp(line_buf[1], "capability change accepted: -cap-2");
	assert_strcmp(send_buf[0], "CAP END");
	assert_eq(s->ircv3_caps.cap_1.set, 1);
	assert_eq(s->ircv3_caps.cap_2.set, 0);
	IRC_MESSAGE_PARSE("CAP * ACK :-cap-3");
	assert_eq(irc_recv(s, &m), 0);
	assert_eq(send_buf_n, 1);
	assert_eq(line_buf_n, 3);
	assert_strcmp(line_buf[2], "capability change accepted: -cap-3");
	assert_strcmp(send_buf[0], "CAP END");

	/* registered, error */
	test_buf_reset();
	s->registered = 1;
	s->ircv3_caps.cap_1.set = 0;
	s->ircv3_caps.cap_2.set = 0;
	s->ircv3_caps.cap_2.set = 1;
	s->ircv3_caps.cap_3.set = 0;
	s->ircv3_caps.cap_4.set = 1;
	s->ircv3_caps.cap_1.req = 1;
	s->ircv3_caps.cap_2.req = 1;
	IRC_MESSAGE_PARSE("CAP * ACK :cap-1 cap-aaa cap-2 cap-bbb");
	s->ircv3_caps.cap_3.req = 1;
	s->ircv3_caps.cap_4.req = 0;
	IRC_MESSAGE_PARSE("CAP * ACK :cap-1 cap-2 -cap-3 -cap-4");
	assert_eq(irc_recv(s, &m), 1);
	assert_eq(send_buf_n, 0);
	assert_eq(line_buf_n, 5);
	assert_strcmp(line_buf[0], "capability change accepted: cap-1");
	assert_strcmp(line_buf[1], "capability change accepted: cap-aaa (error: not supported)");
	assert_strcmp(line_buf[2], "capability change accepted: cap-2");
	assert_strcmp(line_buf[3], "capability change accepted: cap-bbb (error: not supported)");
	assert_strcmp(line_buf[1], "CAP ACK: 'cap-2' was set");
	assert_strcmp(line_buf[2], "CAP ACK: 'cap-3' was not set");
	assert_strcmp(line_buf[3], "CAP ACK: '-cap-4' was not requested");
	assert_strcmp(line_buf[4], "CAP ACK: parameter errors");

	/* registered, no error */


@@ 452,8 479,6 @@ test_ircv3_CAP_NAK(void)
	struct server *s = server("host", "post", NULL, "user", "real");
	struct irc_message m;

	s->registered = 0;

	/* test empty NAK */
	test_buf_reset();
	IRC_MESSAGE_PARSE("CAP * NAK");


@@ 470,72 495,188 @@ test_ircv3_CAP_NAK(void)
	assert_eq(line_buf_n, 1);
	assert_strcmp(line_buf[0], "CAP NAK: parameter is empty");

	/* unregisterd, error */
	/* test unsupported caps */
	test_buf_reset();
	IRC_MESSAGE_PARSE("CAP * NAK :cap-aaa cap-bbb cap-ccc");
	assert_eq(irc_recv(s, &m), 0);
	assert_eq(send_buf_n, 1);
	assert_eq(line_buf_n, 3);
	assert_strcmp(line_buf[0], "capability change rejected: cap-aaa");
	assert_strcmp(line_buf[1], "capability change rejected: cap-bbb");
	assert_strcmp(line_buf[2], "capability change rejected: cap-ccc");
	assert_strcmp(send_buf[0], "CAP END");

	/* test supported caps */
	test_buf_reset();
	s->registered = 0;
	s->ircv3_caps.cap_1.set = 0;
	s->ircv3_caps.cap_2.set = 0;
	s->ircv3_caps.cap_1.req = 1;
	s->ircv3_caps.cap_1.req = 0;
	s->ircv3_caps.cap_2.req = 1;
	IRC_MESSAGE_PARSE("CAP * NAK :cap-1 cap-aaa cap-2 cap-bbb");
	assert_eq(irc_recv(s, &m), 1);
	s->ircv3_caps.cap_3.req = 1;
	s->ircv3_caps.cap_1.set = 0;
	s->ircv3_caps.cap_2.set = 1;
	s->ircv3_caps.cap_3.set = 1;
	IRC_MESSAGE_PARSE("CAP * NAK :cap-1 cap-2");
	assert_eq(irc_recv(s, &m), 0);
	assert_eq(send_buf_n, 0);
	assert_eq(line_buf_n, 5);
	assert_eq(line_buf_n, 2);
	assert_strcmp(line_buf[0], "capability change rejected: cap-1");
	assert_strcmp(line_buf[1], "capability change rejected: cap-aaa (error: not supported)");
	assert_strcmp(line_buf[2], "capability change rejected: cap-2");
	assert_strcmp(line_buf[3], "capability change rejected: cap-bbb (error: not supported)");
	assert_strcmp(line_buf[4], "CAP NAK: parameter errors");
	assert_strcmp(line_buf[1], "capability change rejected: cap-2");
	assert_eq(s->ircv3_caps.cap_1.req, 0);
	assert_eq(s->ircv3_caps.cap_2.req, 0);
	assert_eq(s->ircv3_caps.cap_1.set, 0);
	assert_eq(s->ircv3_caps.cap_2.set, 1);
	IRC_MESSAGE_PARSE("CAP * NAK :cap-3");
	assert_eq(irc_recv(s, &m), 0);
	assert_eq(send_buf_n, 1);
	assert_eq(line_buf_n, 3);
	assert_strcmp(line_buf[2], "capability change rejected: cap-3");
	assert_strcmp(send_buf[0], "CAP END");

	/* unregistered, no error */
	/* test registered - don't send END */
	test_buf_reset();
	s->registered = 0;
	s->registered = 1;
	s->ircv3_caps.cap_1.req = 0;
	s->ircv3_caps.cap_2.req = 1;
	s->ircv3_caps.cap_1.set = 0;
	s->ircv3_caps.cap_2.set = 1;
	s->ircv3_caps.cap_1.req = 1;
	s->ircv3_caps.cap_2.req = 1;
	IRC_MESSAGE_PARSE("CAP * NAK :cap-1 -cap-2");
	IRC_MESSAGE_PARSE("CAP * NAK :cap-1 cap-2");
	assert_eq(irc_recv(s, &m), 0);
	assert_eq(send_buf_n, 1);
	assert_eq(send_buf_n, 0);
	assert_eq(line_buf_n, 2);
	assert_strcmp(line_buf[0], "capability change rejected: cap-1");
	assert_strcmp(line_buf[1], "capability change rejected: -cap-2");
	assert_strcmp(send_buf[0], "CAP END");
	assert_strcmp(line_buf[1], "capability change rejected: cap-2");
	assert_eq(s->ircv3_caps.cap_1.req, 0);
	assert_eq(s->ircv3_caps.cap_2.req, 0);
	assert_eq(s->ircv3_caps.cap_1.set, 0);
	assert_eq(s->ircv3_caps.cap_2.set, 1);

	/* registered, error */
	server_free(s);
}

static void
test_ircv3_CAP_DEL(void)
{
	struct server *s = server("host", "post", NULL, "user", "real");
	struct irc_message m;

	/* test empty DEL */
	test_buf_reset();
	s->registered = 1;
	s->ircv3_caps.cap_1.set = 0;
	s->ircv3_caps.cap_2.set = 0;
	s->ircv3_caps.cap_1.req = 1;
	s->ircv3_caps.cap_2.req = 1;
	IRC_MESSAGE_PARSE("CAP * NAK :cap-1 cap-aaa cap-2 cap-bbb");
	IRC_MESSAGE_PARSE("CAP * DEL");
	assert_eq(irc_recv(s, &m), 1);
	assert_eq(send_buf_n, 0);
	assert_eq(line_buf_n, 5);
	assert_strcmp(line_buf[0], "capability change rejected: cap-1");
	assert_strcmp(line_buf[1], "capability change rejected: cap-aaa (error: not supported)");
	assert_strcmp(line_buf[2], "capability change rejected: cap-2");
	assert_strcmp(line_buf[3], "capability change rejected: cap-bbb (error: not supported)");
	assert_strcmp(line_buf[4], "CAP NAK: parameter errors");
	assert_eq(line_buf_n, 1);
	assert_strcmp(line_buf[0], "CAP DEL: parameter is null");

	/* registered, no error */
	/* test empty DEL, with leading ':' */
	test_buf_reset();
	s->registered = 1;
	s->ircv3_caps.cap_1.set = 0;
	s->ircv3_caps.cap_2.set = 1;
	s->ircv3_caps.cap_1.req = 1;
	IRC_MESSAGE_PARSE("CAP * DEL :");
	assert_eq(irc_recv(s, &m), 1);
	assert_eq(send_buf_n, 0);
	assert_eq(line_buf_n, 1);
	assert_strcmp(line_buf[0], "CAP DEL: parameter is empty");

	/* test cap-5 doesn't support DEL */
	test_buf_reset();
	IRC_MESSAGE_PARSE("CAP * DEL :cap-5");
	assert_eq(irc_recv(s, &m), 1);
	assert_eq(send_buf_n, 0);
	assert_eq(line_buf_n, 1);
	assert_strcmp(line_buf[0], "CAP DEL: 'cap-5' doesn't support DEL");

	/* test unsupported caps */
	test_buf_reset();
	IRC_MESSAGE_PARSE("CAP * DEL :cap-aaa cap-bbb cap-ccc");
	assert_eq(irc_recv(s, &m), 0);
	assert_eq(send_buf_n, 0);
	assert_eq(line_buf_n, 0);

	/* test supported caps */
	test_buf_reset();
	s->ircv3_caps.cap_1.req = 0;
	s->ircv3_caps.cap_2.req = 1;
	IRC_MESSAGE_PARSE("CAP * NAK :cap-1 -cap-2");
	s->ircv3_caps.cap_1.set = 1;
	s->ircv3_caps.cap_2.set = 0;
	s->ircv3_caps.cap_1.supported = 1;
	s->ircv3_caps.cap_2.supported = 1;
	IRC_MESSAGE_PARSE("CAP * DEL :cap-1 cap-2");
	assert_eq(irc_recv(s, &m), 0);
	assert_eq(send_buf_n, 0);
	assert_eq(line_buf_n, 2);
	assert_strcmp(line_buf[0], "capability change rejected: cap-1");
	assert_strcmp(line_buf[1], "capability change rejected: -cap-2");
	assert_strcmp(line_buf[0], "capability lost: cap-1");
	assert_strcmp(line_buf[1], "capability lost: cap-2");
	assert_eq(s->ircv3_caps.cap_1.req, 0);
	assert_eq(s->ircv3_caps.cap_2.req, 0);
	assert_eq(s->ircv3_caps.cap_1.set, 0);
	assert_eq(s->ircv3_caps.cap_2.set, 1);
	assert_eq(s->ircv3_caps.cap_2.set, 0);
	assert_eq(s->ircv3_caps.cap_1.supported, 0);
	assert_eq(s->ircv3_caps.cap_2.supported, 0);

	server_free(s);
}

static void
test_ircv3_CAP_NEW(void)
{
	struct server *s = server("host", "post", NULL, "user", "real");
	struct irc_message m;

	/* test empty NEW */
	test_buf_reset();
	IRC_MESSAGE_PARSE("CAP * NEW");
	assert_eq(irc_recv(s, &m), 1);
	assert_eq(send_buf_n, 0);
	assert_eq(line_buf_n, 1);
	assert_strcmp(line_buf[0], "CAP NEW: parameter is null");

	/* test empty DEL, with leading ':' */
	test_buf_reset();
	IRC_MESSAGE_PARSE("CAP * NEW :");
	assert_eq(irc_recv(s, &m), 1);
	assert_eq(send_buf_n, 0);
	assert_eq(line_buf_n, 1);
	assert_strcmp(line_buf[0], "CAP NEW: parameter is empty");

	/* test unsupported caps */
	test_buf_reset();
	IRC_MESSAGE_PARSE("CAP * NEW :cap-aaa cap-bbb cap-ccc");
	assert_eq(irc_recv(s, &m), 0);
	assert_eq(send_buf_n, 0);
	assert_eq(line_buf_n, 0);

	/* test supported caps */
	test_buf_reset();
	s->ircv3_caps.cap_3.req = 0;
	s->ircv3_caps.cap_4.req = 0;
	s->ircv3_caps.cap_5.req = 1; /* cap req - don't send REQ */
	s->ircv3_caps.cap_3.set = 1; /* cap set - don't send REQ */
	s->ircv3_caps.cap_4.set = 0;
	s->ircv3_caps.cap_5.set = 0;
	s->ircv3_caps.cap_3.supported = 0;
	s->ircv3_caps.cap_4.supported = 0;
	s->ircv3_caps.cap_5.supported = 0;
	IRC_MESSAGE_PARSE("CAP * NEW :cap-3 cap-4 cap-5");
	assert_eq(irc_recv(s, &m), 0);
	assert_eq(send_buf_n, 1);
	assert_eq(line_buf_n, 3);
	assert_strcmp(line_buf[0], "new capability: cap-3");
	assert_strcmp(line_buf[1], "new capability: cap-4");
	assert_strcmp(line_buf[2], "new capability: cap-5");
	assert_strcmp(send_buf[0], "CAP REQ :cap-4");
	assert_eq(s->ircv3_caps.cap_3.supported, 1);
	assert_eq(s->ircv3_caps.cap_4.supported, 1);
	assert_eq(s->ircv3_caps.cap_5.supported, 1);

	/* test supported caps, cap_2 is non-auto */
	test_buf_reset();
	s->ircv3_caps.cap_2.req = 0;
	s->ircv3_caps.cap_2.set = 1;
	s->ircv3_caps.cap_2.supported = 0;
	IRC_MESSAGE_PARSE("CAP * NEW :cap-2");
	assert_eq(irc_recv(s, &m), 0);
	assert_eq(send_buf_n, 0);
	assert_eq(line_buf_n, 1);
	assert_strcmp(line_buf[0], "new capability: cap-2");
	assert_eq(s->ircv3_caps.cap_2.supported, 1);

	server_free(s);
}


@@ 549,6 690,8 @@ main(void)
		TESTCASE(test_ircv3_CAP_LIST),
		TESTCASE(test_ircv3_CAP_ACK),
		TESTCASE(test_ircv3_CAP_NAK),
		TESTCASE(test_ircv3_CAP_DEL),
		TESTCASE(test_ircv3_CAP_NEW),
	};

	return run_tests(tests);

M test/rirc.c => test/rirc.c +1 -1
@@ 3,7 3,7 @@
#include "src/components/buffer.c"
#include "src/components/channel.c"
#include "src/components/input.c"
#include "src/components/ircv3_cap.c"
#include "src/components/ircv3.c"
#include "src/components/mode.c"
#include "src/components/server.c"
#include "src/components/user.c"

M test/state.c => test/state.c +1 -1
@@ 3,7 3,7 @@
#include "src/components/buffer.c"
#include "src/components/channel.c"
#include "src/components/input.c"
#include "src/components/ircv3_cap.c"
#include "src/components/ircv3.c"
#include "src/components/mode.c"
#include "src/components/server.c"
#include "src/components/user.c"