# HG changeset patch # User David Demelier # Date 1610703525 -3600 # Node ID d63a360811ddd17a75d78dbfaf5f5a9919b7c0be # Parent aef1568a76bf4284ed270ef758f36961c0135b53 irccd: add rules diff -r aef1568a76bf -r d63a360811dd .hgignore --- a/.hgignore Thu Jan 14 18:56:00 2021 +0100 +++ b/.hgignore Fri Jan 15 10:38:45 2021 +0100 @@ -32,6 +32,7 @@ # tests. ^tests/test-dl-plugin$ ^tests/test-log$ +^tests/test-rule$ ^tests/test-subst$ ^tests/test-util$ diff -r aef1568a76bf -r d63a360811dd Makefile --- a/Makefile Thu Jan 14 18:56:00 2021 +0100 +++ b/Makefile Fri Jan 15 10:38:45 2021 +0100 @@ -47,6 +47,7 @@ LIBIRCCD_SRCS+= lib/irccd/log.c LIBIRCCD_SRCS+= lib/irccd/peer.c LIBIRCCD_SRCS+= lib/irccd/plugin.c +LIBIRCCD_SRCS+= lib/irccd/rule.c LIBIRCCD_SRCS+= lib/irccd/server.c LIBIRCCD_SRCS+= lib/irccd/subst.c LIBIRCCD_SRCS+= lib/irccd/transport.c @@ -71,8 +72,9 @@ TESTS= tests/test-dl-plugin.c TESTS+= tests/test-log.c +TESTS+= tests/test-rule.c +TESTS+= tests/test-subst.c TESTS+= tests/test-util.c -TESTS+= tests/test-subst.c TESTS_OBJS= ${TESTS:.c=} DEFINES= -D_BSD_SOURCE @@ -133,7 +135,7 @@ ${LIBIRCCD_OBJS}: ${LIBCOMPAT} lib/irccd/config.h -${LIBIRCCD}: ${LIBIRCCD_OBJS} +${LIBIRCCD}: ${LIBIRCCD_OBJS} ${LIBDUKTAPE} ${CMD.ar} ${IRCCD}: ${IRCCD_OBJS} ${LIBCOMPAT} ${LIBDUKTAPE} ${LIBIRCCD} @@ -143,10 +145,8 @@ ${CMD.ccld} # Unit tests. -tests/test-%.o: tests/test-%.c - ${CMD.cc} -tests/test-%: tests/test-%.o ${LIBCOMPAT} ${IRCCD_OBJS} - ${CMD.ccld} +tests/test-%: tests/test-%.c + ${CC} ${DEFINES} ${INCS} ${CFLAGS} -o $@ $< ${LIBS} ${LDFLAGS} ${TESTS_OBJS}: ${LIBIRCCD} diff -r aef1568a76bf -r d63a360811dd irccd/main.c --- a/irccd/main.c Thu Jan 14 18:56:00 2021 +0100 +++ b/irccd/main.c Fri Jan 15 10:38:45 2021 +0100 @@ -27,40 +27,49 @@ #include #include +#include + static struct irc_plugin js = { .name = "example" }; +#include + +static void +dump(void) +{ + for (size_t i = 0; i < irc.rulesz; ++i) { + printf("== rule %zd ==\n", i); + printf("servers => %s\n", irc.rules[i].servers); + } +} + int main(int argc, char **argv) { - struct irc_server s = { - .name = "malikania", - .hostname = "malikania.fr", - .port = 6667, - .nickname = "circ", - .username = "circ", - .realname = "circ" - }; - struct irc_server freenode = { - .name = "freenode", - .hostname = "chat.freenode.net", - .port = 6667, - .nickname = "circ", - .username = "circ", - .realname = "circ" - }; + struct irc_rule r = {0}; + + irc_rule_add(r.servers, "malikania"); + //irc_rule_add(r.servers, "freenode"); + + printf("%d\n", irc_rule_match(&r, "malikania", "", "", "", "")); - irc_init(); - irc_log_set_verbose(true); - irc_server_join(&s, "#test", NULL); - irc_transport_bind("/tmp/irccd.sock"); - irc_add_server(&s); - irc_add_server(&freenode); #if 0 - if (!irc_js_plugin_open(&js, "/Users/markand/Dev/irccd-4/test.js")) - return 1; - irc_add_plugin(&js); + irc_rule_add(r.servers, "malikania"); + irc_bot_insert_rule(&r, 0); + strcpy(r.servers, "freenode:"); + irc_bot_insert_rule(&r, 15); + strcpy(r.servers, "oftc:"); + irc_bot_insert_rule(&r, 0); + strcpy(r.servers, "jean:"); + irc_bot_insert_rule(&r, 1); + + puts("BEFORE"); + dump(); + irc_bot_remove_rule(3); + puts("AFTER"); + dump(); #endif - irc_run(); + + } diff -r aef1568a76bf -r d63a360811dd lib/irccd/irccd.c --- a/lib/irccd/irccd.c Thu Jan 14 18:56:00 2021 +0100 +++ b/lib/irccd/irccd.c Fri Jan 15 10:38:45 2021 +0100 @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -30,11 +31,9 @@ #include "server.h" #include "peer.h" #include "transport.h" +#include "rule.h" #include "util.h" -/* TODO: TMP */ -#include - #define APPEND(a, l, o, f) \ do { \ a = irc_util_reallocarray(a, ++l, sizeof (*o)); \ @@ -285,6 +284,36 @@ REMOVE(irc.plugins, irc.pluginsz, cmp_plugin); } +bool +irc_bot_insert_rule(const struct irc_rule *rule, size_t i) +{ + assert(rule); + + if (irc.rulesz >= IRC_RULE_MAX) { + errno = ENOMEM; + return false; + } + + if (i >= irc.rulesz) + i = irc.rulesz; + + memmove(&irc.rules[i + 1], &irc.rules[i], sizeof (*irc.rules) * (irc.rulesz++ - i)); + memcpy(&irc.rules[i], rule, sizeof (*rule)); + + return true; +} + +void +irc_bot_remove_rule(size_t i) +{ + assert(i < irc.rulesz); + + if (i + 1 >= irc.rulesz) + irc.rulesz--; + else + memmove(&irc.rules[i], &irc.rules[i + 1], sizeof (*irc.rules) * (irc.rulesz-- - i)); +} + void irc_post(void (*exec)(void *), void *data) { diff -r aef1568a76bf -r d63a360811dd lib/irccd/irccd.h --- a/lib/irccd/irccd.h Thu Jan 14 18:56:00 2021 +0100 +++ b/lib/irccd/irccd.h Fri Jan 15 10:38:45 2021 +0100 @@ -19,12 +19,18 @@ #ifndef IRCCD_H #define IRCCD_H +#include #include +#include "rule.h" + struct irc_server; struct irc_plugin; struct irc_peer; + +#define IRC_BOT_RULE_MAX 256 + extern struct irc { struct irc_peer *peers; size_t peersz; @@ -32,6 +38,8 @@ size_t pluginsz; struct irc_server *servers; size_t serversz; + struct irc_rule rules[IRC_BOT_RULE_MAX]; + size_t rulesz; } irc; void @@ -55,6 +63,12 @@ void irc_del_plugin(const char *); +bool +irc_bot_insert_rule(const struct irc_rule *, size_t); + +void +irc_bot_remove_rule(size_t); + void irc_post(void (*)(void *), void *); diff -r aef1568a76bf -r d63a360811dd lib/irccd/rule.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/rule.c Fri Jan 15 10:38:45 2021 +0100 @@ -0,0 +1,154 @@ +/* + * rule.c -- rule filtering + * + * Copyright (c) 2013-2021 David Demelier + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include + +#include "rule.h" +#include "util.h" + +static void +lower(char *dst, const char *src) +{ + while (*src) + *dst++ = tolower(*src++); +} + +static inline char * +find(const char *str, const char *value) +{ + char strlower[IRC_RULE_MAX] = {0}; + char valuelower[IRC_RULE_MAX] = {0}; + char *p; + + lower(strlower, str); + lower(valuelower, value); + + if ((p = strstr(strlower, valuelower))) + return (char *)&str[p - strlower]; + + return NULL; +} + +static inline bool +match(const char *str, const char *value) +{ + size_t len = strlen(value); + const char *p; + + if (!str[0]) + return true; + if (len == 0 || !(p = find(str, value))) + return false; + + /* + * Consider the following scenario: + * + * value = no + * str = mlk:freenode: + * p = ^ + */ + while (p != str && *p != ':') + --p; + if (*p == ':') + ++p; + + return strncasecmp(p, value, len) == 0; +} + +bool +irc_rule_add(char *str, const char *value) +{ + size_t slen, vlen; + + if (find(str, value)) + return true; + + slen = strlen(str); + vlen = strlen(value); + + if (vlen + 1 >= IRC_RULE_MAX - slen) { + errno = ENOMEM; + return false; + } + + sprintf(&str[slen], "%s:", value); + + return true; +} + +void +irc_rule_remove(char *str, const char *value) +{ + char *pos; + size_t vlen, slen; + + if (!(pos = find(str, value))) + return; + + slen = strlen(str); + vlen = strlen(value) + 1; /* includes ':' */ + + assert(pos[vlen - 1] == ':'); + memmove(&pos[0], &pos[vlen], IRC_RULE_MAX - (&pos[vlen] - str)); +} + +bool +irc_rule_match(const struct irc_rule *rule, + const char *server, + const char *channel, + const char *origin, + const char *plugin, + const char *event) +{ + return match(rule->servers, server) && + match(rule->channels, channel) && + match(rule->origins, origin) && + match(rule->plugins, plugin) && + match(rule->events, event); +} + +bool +irc_rule_matchlist(const struct irc_rule *rules, + size_t rulesz, + const char *server, + const char *channel, + const char *origin, + const char *plugin, + const char *event) +{ + bool result = true; + + for (size_t i = 0; i < rulesz; ++i) + if (irc_rule_match(&rules[i], server, channel, origin, plugin, event)) + result = rules[i].action == IRC_RULE_ACCEPT; + + return result; +} + +void +irc_rule_finish(struct irc_rule *rule) +{ + assert(rule); + + memset(rule, 0, sizeof (*rule)); +} diff -r aef1568a76bf -r d63a360811dd lib/irccd/rule.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/rule.h Fri Jan 15 10:38:45 2021 +0100 @@ -0,0 +1,67 @@ +/* + * rule.h -- rule filtering + * + * Copyright (c) 2013-2021 David Demelier + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_RULE_H +#define IRCCD_RULE_H + +#include +#include + +#define IRC_RULE_MAX 1024 + +enum irc_rule_action { + IRC_RULE_ACCEPT, + IRC_RULE_DROP +}; + +struct irc_rule { + enum irc_rule_action action; + char servers[IRC_RULE_MAX]; + char channels[IRC_RULE_MAX]; + char origins[IRC_RULE_MAX]; + char plugins[IRC_RULE_MAX]; + char events[IRC_RULE_MAX]; +}; + +bool +irc_rule_add(char *, const char *); + +void +irc_rule_remove(char *, const char *); + +bool +irc_rule_match(const struct irc_rule *, + const char *, + const char *, + const char *, + const char *, + const char *); + +bool +irc_rule_matchlist(const struct irc_rule *, + size_t, + const char *, + const char *, + const char *, + const char *, + const char *); + +void +irc_rule_finish(struct irc_rule *); + +#endif /* !IRCCD_RULE_H */ diff -r aef1568a76bf -r d63a360811dd tests/test-rule.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-rule.c Fri Jan 15 10:38:45 2021 +0100 @@ -0,0 +1,321 @@ +/* + * test-rule.c -- test rule.h functions + * + * Copyright (c) 2013-2021 David Demelier + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define GREATEST_USE_ABBREVS 0 +#include + +#include +#include + +static void +clean(void *udata) +{ + (void)udata; + + memset(&irc, 0, sizeof (irc)); +} + +GREATEST_TEST +basics_insert(void) +{ + struct irc_rule r1 = {0}, r2 = {0}; + + irc_rule_add(r1.servers, "s1"); + irc_rule_add(r2.servers, "s2"); + + irc_bot_insert_rule(&r1, 0); + irc_bot_insert_rule(&r2, 0); + + GREATEST_ASSERT_EQ(2U, irc.rulesz); + GREATEST_ASSERT(memcmp(&irc.rules[0], &r2, sizeof (r2)) == 0); + GREATEST_ASSERT(memcmp(&irc.rules[1], &r1, sizeof (r1)) == 0); + GREATEST_PASS(); +} + +GREATEST_TEST +basics_remove(void) +{ + struct irc_rule r1 = {0}, r2 = {0}, r3 = {0}; + + irc_rule_add(r1.servers, "s1"); + irc_rule_add(r2.servers, "s2"); + irc_rule_add(r3.servers, "s3"); + + irc_bot_insert_rule(&r1, -1); + irc_bot_insert_rule(&r2, -1); + irc_bot_insert_rule(&r3, -1); + irc_bot_remove_rule(1); + + GREATEST_ASSERT_EQ(2U, irc.rulesz); + GREATEST_ASSERT(memcmp(&irc.rules[0], &r1, sizeof (r1)) == 0); + GREATEST_ASSERT(memcmp(&irc.rules[1], &r3, sizeof (r3)) == 0); + + irc_bot_remove_rule(1); + GREATEST_ASSERT_EQ(1U, irc.rulesz); + GREATEST_ASSERT(memcmp(&irc.rules[0], &r1, sizeof (r1)) == 0); + + irc_bot_remove_rule(0); + GREATEST_ASSERT_EQ(0U, irc.rulesz); + + GREATEST_PASS(); +} + +/* + * Simulate the following rules configuration: + * + * # + * # On all servers, each channel #staff can't use the onCommand event, + * # everything else is allowed. + * # + * [rule] #1 + * servers = "" + * channels = "#staff" + * events = "onCommand" + * action = drop + * + * # + * # However, the same onCommand on #staff is allowed on server "unsafe" + * # + * [rule] #2 + * servers = "unsafe" + * channels = "#staff" + * events = "onCommand" + * action = accept + * + * # + * # Plugin game is only allowed on server "malikania" and "localhost", + * # channel "#games" and events "onMessage, onCommand". + * # + * # The first rule #3-1 disable the plugin game for every server, it is + * # reenabled again with the #3-2. + * # + * [rule] #3-1 + * plugins = "game" + * action = drop + * + * [rule] #3-2 + * servers = "malikania localhost" + * channels = "#games" + * plugins = "game" + * events = "onMessage onCommand" + * action = accept + */ + +static void +set(void *udata) +{ + (void)udata; + + struct irc_rule r1 = {0}, r2 = {0}, r31 = {0}, r32 = {0}; + + /* Deinit irccd first. */ + memset(&irc, 0, sizeof (irc)); + + /* #1 */ + r1.action = IRC_RULE_DROP; + irc_rule_add(r1.channels, "#staff"); + irc_rule_add(r1.events, "onCommand"); + irc_bot_insert_rule(&r1, -1); + + /* #2 */ + r2.action = IRC_RULE_ACCEPT; + irc_rule_add(r2.servers, "unsafe"); + irc_rule_add(r2.channels, "#staff"); + irc_rule_add(r2.events, "onCommand"); + irc_bot_insert_rule(&r2, -1); + + /* #3-1 */ + r31.action = IRC_RULE_DROP; + irc_rule_add(r31.plugins, "game"); + irc_bot_insert_rule(&r31, -1); + + /* #3-2 */ + r32.action = IRC_RULE_ACCEPT; + irc_rule_add(r32.servers, "malikania"); + irc_rule_add(r32.servers, "localhost"); + irc_rule_add(r32.channels, "#games"); + irc_rule_add(r32.plugins, "game"); + irc_rule_add(r32.events, "onCommand"); + irc_rule_add(r32.events, "onMessage"); + irc_bot_insert_rule(&r32, -1); +}; + +GREATEST_TEST +solve_match1(void) +{ + struct irc_rule m = {0}; + + GREATEST_ASSERT(irc_rule_match(&m, "freenode", "#test", "a", "", "")); + GREATEST_ASSERT(irc_rule_match(&m, "", "", "", "", "")); + GREATEST_PASS(); +} + +GREATEST_TEST +solve_match2(void) +{ + struct irc_rule m = {0}; + + irc_rule_add(m.servers, "freenode"); + + GREATEST_ASSERT(irc_rule_match(&m, "FreeNode", "#test", "a", "", "")); + GREATEST_ASSERT(!irc_rule_match(&m, "malikania", "#test", "a", "", "")); + GREATEST_ASSERT(irc_rule_match(&m, "freenode", "", "jean", "", "onMessage")); + GREATEST_PASS(); +} + +GREATEST_TEST +solve_match3(void) +{ + struct irc_rule m = {0}; + + irc_rule_add(m.servers, "freenode"); + irc_rule_add(m.channels, "#staff"); + + GREATEST_ASSERT(irc_rule_match(&m, "freenode", "#staff", "a", "", "")); + GREATEST_ASSERT(!irc_rule_match(&m, "freenode", "#test", "a", "", "")); + GREATEST_ASSERT(!irc_rule_match(&m, "malikania", "#staff", "a", "", "")); + GREATEST_PASS(); +} + +GREATEST_TEST +solve_match4(void) +{ + struct irc_rule m = {0}; + + irc_rule_add(m.servers, "malikania"); + irc_rule_add(m.channels, "#staff"); + irc_rule_add(m.origins, "a"); + + GREATEST_ASSERT(irc_rule_match(&m, "malikania", "#staff", "a", "","")); + GREATEST_ASSERT(!irc_rule_match(&m, "malikania", "#staff", "b", "", "")); + GREATEST_ASSERT(!irc_rule_match(&m, "freenode", "#staff", "a", "", "")); + GREATEST_PASS(); +} + +GREATEST_TEST +solve_match5(void) +{ + struct irc_rule m = {0}; + + irc_rule_add(m.servers, "malikania"); + irc_rule_add(m.servers, "freenode"); + + GREATEST_ASSERT(irc_rule_match(&m, "malikania", "", "", "", "")); + GREATEST_ASSERT(irc_rule_match(&m, "freenode", "", "", "", "")); + GREATEST_ASSERT(!irc_rule_match(&m, "no", "", "", "", "")); + GREATEST_PASS(); +} + +GREATEST_TEST +solve_match6(void) +{ + struct irc_rule m = {0}; + + irc_rule_add(m.servers, "malikania"); + irc_rule_add(m.origins, "markand"); + + GREATEST_ASSERT(irc_rule_match(&m, "malikania", "#staff", "markand", "system", "onCommand")); + GREATEST_ASSERT(!irc_rule_match(&m, "malikania", "#staff", "", "system", "onNames")); + GREATEST_ASSERT(!irc_rule_match(&m, "malikania", "#staff", "jean", "system", "onMessage")); + GREATEST_PASS(); +} + +GREATEST_TEST +solve_match7(void) +{ + /* Allowed */ + GREATEST_ASSERT(irc_rule_matchlist(irc.rules, irc.rulesz, "malikania", "#staff", "", "a", "onMessage")); + + /* Allowed */ + GREATEST_ASSERT(irc_rule_matchlist(irc.rules, irc.rulesz, "freenode", "#staff", "", "b", "onTopic")); + + /* Not allowed */ + GREATEST_ASSERT(!irc_rule_matchlist(irc.rules, irc.rulesz, "malikania", "#staff", "", "", "onCommand")); + + /* Not allowed */ + GREATEST_ASSERT(!irc_rule_matchlist(irc.rules, irc.rulesz, "freenode", "#staff", "", "c", "onCommand")); + + /* Allowed */ + GREATEST_ASSERT(irc_rule_matchlist(irc.rules, irc.rulesz, "unsafe", "#staff", "", "c", "onCommand")); + + GREATEST_PASS(); +} + +GREATEST_TEST +solve_match8(void) +{ + /* Allowed */ + GREATEST_ASSERT(irc_rule_matchlist(irc.rules, irc.rulesz, "malikania", "#games", "", "game", "onMessage")); + + /* Allowed */ + GREATEST_ASSERT(irc_rule_matchlist(irc.rules, irc.rulesz, "localhost", "#games", "", "game", "onMessage")); + + /* Allowed */ + GREATEST_ASSERT(irc_rule_matchlist(irc.rules, irc.rulesz, "malikania", "#games", "", "game", "onCommand")); + + /* Not allowed */ + GREATEST_ASSERT(!irc_rule_matchlist(irc.rules, irc.rulesz, "malikania", "#games", "", "game", "onQuery")); + + /* Not allowed */ + GREATEST_ASSERT(!irc_rule_matchlist(irc.rules, irc.rulesz, "freenode", "#no", "", "game", "onMessage")); + + /* Not allowed */ + GREATEST_ASSERT(!irc_rule_matchlist(irc.rules, irc.rulesz, "malikania", "#test", "", "game", "onMessage")); + GREATEST_PASS(); +} + +GREATEST_TEST +solve_match9(void) +{ + GREATEST_ASSERT(!irc_rule_matchlist(irc.rules, irc.rulesz, "MALIKANIA", "#STAFF", "", "SYSTEM", "onCommand")); + GREATEST_PASS(); +} + +GREATEST_SUITE(suite_basics) +{ + GREATEST_SET_SETUP_CB(clean, NULL); + GREATEST_RUN_TEST(basics_insert); + GREATEST_RUN_TEST(basics_remove); +} + +GREATEST_SUITE(suite_solve) +{ + GREATEST_SET_SETUP_CB(set, NULL); + GREATEST_RUN_TEST(solve_match1); + GREATEST_RUN_TEST(solve_match2); + GREATEST_RUN_TEST(solve_match3); + GREATEST_RUN_TEST(solve_match4); + GREATEST_RUN_TEST(solve_match5); + GREATEST_RUN_TEST(solve_match6); + GREATEST_RUN_TEST(solve_match7); + GREATEST_RUN_TEST(solve_match8); + GREATEST_RUN_TEST(solve_match9); +} + +GREATEST_MAIN_DEFS(); + +int +main(int argc, char **argv) +{ + GREATEST_MAIN_BEGIN(); + GREATEST_RUN_SUITE(suite_basics); + GREATEST_RUN_SUITE(suite_solve); + GREATEST_MAIN_END(); + + return 0; +}