From 7109ae6b702c309f857d4dd11a404d6a60015670 Mon Sep 17 00:00:00 2001 From: Alex Denes Date: Fri, 5 Mar 2021 11:26:31 +0000 Subject: [PATCH] Fix CRLF removal bug, change some of the assembly and parsing logic and add tests back --- CMakeLists.txt | 21 ++++--- include/memory.h | 7 +++ src/assemblers/message.c | 17 +++++- src/assemblers/tag.c | 19 +------ src/memory/allocators.c | 2 - src/memory/free.c | 2 +- src/memory/listmgrs.c | 120 +++++++++++++++++++++++++++++++++++++++ src/tokenizers/message.c | 18 +++--- tests/CMakeLists.txt | 5 ++ tests/common.c | 82 ++++++++++++++++++++++++++ tests/common.h | 40 +++++++++++++ tests/fullloop.c | 61 ++++++++++++++++++++ 12 files changed, 357 insertions(+), 37 deletions(-) create mode 100644 src/memory/listmgrs.c create mode 100644 tests/CMakeLists.txt create mode 100644 tests/common.c create mode 100644 tests/common.h create mode 100644 tests/fullloop.c diff --git a/CMakeLists.txt b/CMakeLists.txt index e75685b..4ee0b75 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.16) project( uIRC - VERSION 0.2.4 + VERSION 0.3.0 DESCRIPTION "Simple and lightweight IRC protocol helper" LANGUAGES C ) @@ -11,11 +11,12 @@ include(GNUInstallDirs) set(UIRC_VERSION "${PROJECT_VERSION}") add_compile_definitions(UIRC_VERSION="${UIRC_VERSION}") -OPTION(BUILD_IRCV3 "Build IRCv3 components" OFF) -OPTION(BUILD_VALIDATORS "Build message validators" OFF) -OPTION(BUILD_DOCS "Build documentation with doxygen" OFF) -OPTION(CODE_ANALYZER "Analyze the code statically" OFF) -OPTION(CODE_COVERAGE "Build with coverage tools" OFF) +OPTION(BUILD_IRCV3 "Build IRCv3 components" OFF) +OPTION(BUILD_VALIDATORS "Build message validators" OFF) +OPTION(BUILD_DOCS "Build documentation with doxygen" OFF) +OPTION(BUILD_TESTS "Build tests that check the library compliance" OFF) +OPTION(CODE_ANALYZER "Analyze the code statically" OFF) +OPTION(CODE_COVERAGE "Build with coverage tools" OFF) set(UIRC_SOURCE src/assemblers/message.c @@ -24,6 +25,7 @@ set(UIRC_SOURCE src/tokenizers/user.c src/memory/allocators.c src/memory/free.c + src/memory/listmgrs.c ) set(UIRC_HEADERS @@ -84,7 +86,12 @@ if(BUILD_DOCS) ) endif() endif() - +if (BUILD_TESTS) + message(STATUS "Tests are going to be built.") + add_subdirectory(tests) + enable_testing() + add_test(NAME "FullLoop" COMMAND "fullloop") +endif() add_library(uirc ${UIRC_SOURCE}) target_link_libraries(uirc ${UIRC_LIBS}) diff --git a/include/memory.h b/include/memory.h index 5f318e4..f164417 100644 --- a/include/memory.h +++ b/include/memory.h @@ -32,6 +32,13 @@ IRC_Message* uirc_malloc_message(const char* command, ...); IRC_Buffer* uirc_malloc_buffer(const char* name, const char* topic, const char* key); IRC_Network* uirc_malloc_network(const char* addr, const char* svc, const char* pass); +bool uirc_list_add_buffer(IRC_Network* net, IRC_Buffer* buf); +bool uirc_list_add_user(IRC_Buffer* buf, IRC_User* usr); +bool uirc_list_add_message(IRC_Buffer* buf, IRC_Message* msg); +bool uirc_list_remove_buffer(IRC_Network* net, IRC_Buffer* buf); +bool uirc_list_remove_user(IRC_Buffer* buf, IRC_User* usr); +bool uirc_list_remove_message(IRC_Buffer* buf, IRC_Message* msg); + #ifdef UIRC_FEATURE_IRCV3 void uirc_free_tag(IRC_Tag* t); void uirc_free_capability(IRC_Capability* c); diff --git a/src/assemblers/message.c b/src/assemblers/message.c index 94b134e..b6e9147 100644 --- a/src/assemblers/message.c +++ b/src/assemblers/message.c @@ -33,12 +33,25 @@ uirc_assembler_message(char* buf, const IRC_Message* m, size_t len) #ifdef UIRC_FEATURE_IRCV3 if (m->tag_list != NULL) { + + if (len > 1) { + *(buf++) = '@'; + len--; + } else + return -1; + ssize_t ret; if ((ret = uirc_assembler_tag_list(buf, m->tag_list, len)) >= 0) { buf += (size_t) ret; len -= (size_t) ret; } else return ret; + + if (len > 1) { + *(buf++) = ' '; + len--; + } else + return -1; } assert(sv <= buf); #endif /* UIRC_FEATURE_IRCV3 */ @@ -67,7 +80,7 @@ uirc_assembler_message(char* buf, const IRC_Message* m, size_t len) if (m->command != NULL) { if (len < 2) return -1; int ret; - if ((ret = snprintf(buf, len, "%s ", m->command)) >= 0) { + if ((ret = snprintf(buf, len, "%s", m->command)) >= 0) { buf += (size_t) ret; len -= (size_t) ret; } else @@ -79,7 +92,7 @@ uirc_assembler_message(char* buf, const IRC_Message* m, size_t len) for (unsigned int i = 0; i < IRC_MAXARGS && m->args[i] != NULL; i++) { if (len < 2) return -1; int ret; - if ((ret = snprintf(buf, len, (i < IRC_MAXARGS - 1 && m->args[i + 1] == NULL && m->trailing) ? ":%s" : "%s ", m->args[i])) >= 0) { + if ((ret = snprintf(buf, len, (m->args[i + 1] == NULL && m->trailing) ? " :%s" : " %s", m->args[i])) >= 0) { buf += (size_t) ret; len -= (size_t) ret; } else diff --git a/src/assemblers/tag.c b/src/assemblers/tag.c index 60db2b5..e64956c 100644 --- a/src/assemblers/tag.c +++ b/src/assemblers/tag.c @@ -68,14 +68,8 @@ uirc_assembler_tag_list(char* buf, const llist_t* tl, size_t len) assert(buf != NULL); const char* const sv = buf; - for (; tl->content != NULL && ((IRC_Tag*) tl->content)->key != NULL;) { - if (buf == sv) { - if (len > 1) { - *(buf++) = '@'; - len--; - } else - return -1; - } else { + for (; tl != NULL && tl->content != NULL && ((IRC_Tag*) tl->content)->key != NULL; tl = tl->next) { + if (buf != sv) { if (len > 1) { *(buf++) = ';'; len--; @@ -89,15 +83,6 @@ uirc_assembler_tag_list(char* buf, const llist_t* tl, size_t len) len -= (size_t) ret; } else return -1; - - if ((tl = tl->next) == NULL) { - if (len > 1) { - *(buf++) = ' '; - len--; - } else - return -1; - break; - } } return buf - sv; } diff --git a/src/memory/allocators.c b/src/memory/allocators.c index 9d98ded..e9882bf 100644 --- a/src/memory/allocators.c +++ b/src/memory/allocators.c @@ -20,7 +20,6 @@ #include "types.h" // IRC_* #include // assert() -#include // llist_t remove_linked_list_elem() #include // malloc_string() #include // va_*() va_list #include // free() @@ -183,4 +182,3 @@ uirc_malloc_network(const char* addr, const char* svc, const char* pass) } return NULL; } - diff --git a/src/memory/free.c b/src/memory/free.c index 6ab9306..6ab4767 100644 --- a/src/memory/free.c +++ b/src/memory/free.c @@ -63,7 +63,7 @@ uirc_free_message(IRC_Message* m) } free(m->command); - for (unsigned short i = 0; m->args[i] != NULL; i++) free(m->args[i]); + for (unsigned short i = 0; i < IRC_MAXARGS && m->args[i] != NULL; i++) free(m->args[i]); #ifdef UIRC_FEATURE_IRCV3 for (llist_t* t = m->tag_list; t != NULL; t = t->next) { diff --git a/src/memory/listmgrs.c b/src/memory/listmgrs.c new file mode 100644 index 0000000..72d6e12 --- /dev/null +++ b/src/memory/listmgrs.c @@ -0,0 +1,120 @@ +/* + * This file is part of uIRC. (https://git.redxen.eu/caskd/uIRC) + * Copyright (c) 2019, 2020 Alex-David Denes + * + * uIRC is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * uIRC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with uIRC. If not, see . + */ + +#include "memory.h" +#include "types.h" // IRC_* + +#include // assert() +#include // llist_t remove_linked_list_elem() +#include // bool +#include // free() + +// NOTE: yes, i know these repeat a FUCKTON, need to find a way to use the preprocessor to merge them or create a generalized function + +bool +uirc_list_add_message(IRC_Buffer* buf, IRC_Message* msg) +{ + assert(buf != NULL); + if (msg == NULL) return 0; + llist_t* tmp = NULL; + if ((tmp = allocate_linked_list_elem(0)) == NULL) return 0; + if (buf->message_list != NULL) connect_linked_list_elem(buf->message_list, tmp); + else + buf->message_list = tmp; + tmp->content = buf; + return 1; +} + +bool +uirc_list_add_buffer(IRC_Network* net, IRC_Buffer* buf) +{ + assert(net != NULL); + if (buf == NULL) return 0; + llist_t* tmp = NULL; + if ((tmp = allocate_linked_list_elem(0)) == NULL) return 0; + if (net->buf_list != NULL) connect_linked_list_elem(net->buf_list, tmp); + else + net->buf_list = tmp; + tmp->content = buf; + return 1; +} + +bool +uirc_list_add_user(IRC_Buffer* buf, IRC_User* usr) +{ + assert(buf != NULL); + if (usr == NULL) return 0; + llist_t* tmp = NULL; + if ((tmp = allocate_linked_list_elem(0)) == NULL) return 0; + if (buf->user_list != NULL) connect_linked_list_elem(buf->user_list, tmp); + else + buf->user_list = tmp; + tmp->content = usr; + return 1; +} + +bool +uirc_list_remove_buffer(IRC_Network* net, IRC_Buffer* buf) +{ + assert(net != NULL); + assert(buf != NULL); + for (llist_t* cur = net->buf_list; cur != NULL;) { + if (cur->content == buf) { + if (cur == net->buf_list) net->buf_list = (cur->prev != NULL) ? cur->prev : cur->next; + uirc_free_buffer(buf); + remove_linked_list_elem(cur); + break; + } + if ((cur = cur->next) == NULL) return 0; + } + return 1; +} + +bool +uirc_list_remove_user(IRC_Buffer* buf, IRC_User* usr) +{ + assert(buf != NULL); + assert(usr != NULL); + for (llist_t* cur = buf->user_list; cur != NULL;) { + if (cur->content == usr) { + if (cur == buf->user_list) buf->user_list = (cur->prev != NULL) ? cur->prev : cur->next; + uirc_free_user(usr); + remove_linked_list_elem(cur); + break; + } + if ((cur = cur->next) == NULL) return 0; + } + return 1; +} + +bool +uirc_list_remove_message(IRC_Buffer* buf, IRC_Message* msg) +{ + assert(buf != NULL); + assert(msg != NULL); + for (llist_t* cur = buf->user_list; cur != NULL;) { + if (cur->content == msg) { + if (cur == buf->user_list) buf->user_list = (cur->prev != NULL) ? cur->prev : cur->next; + uirc_free_message(msg); + remove_linked_list_elem(cur); + break; + } + if ((cur = cur->next) == NULL) return 0; + } + return 1; +} diff --git a/src/tokenizers/message.c b/src/tokenizers/message.c index aeea449..08fe4cb 100644 --- a/src/tokenizers/message.c +++ b/src/tokenizers/message.c @@ -35,18 +35,17 @@ uirc_tokenizer_message(const char* str) { assert(str != NULL); - IRC_Message* m = malloc(sizeof(IRC_Message)); + IRC_Message* const m = malloc(sizeof(IRC_Message)); size_t len = strlen(str); // Ignore CRLF at the end, irrelevant for parsing - // NOTE: len can be used directly as it's the lenght of the string without terminating null byte - // this is also the same as the last element in a array (off by one) - if (str[len] == '\n') { + if (str[len - 1] == '\n') { len--; - if (str[len] == '\r') len--; + if (str[len - 1] == '\r') len--; } char* const ws = malloc_string(str, len); // NOTE: Some compilers might warn you about modifying contents of p because it points to ws which - // is "immutable". This is safe to ignore and it's purpose is to not use ws for tokenizing + // is "immutable". This is safe to ignore as the purpose of it is to not use ws for tokenizing and + // to save the original pointer which can be freed char* p = ws; @@ -95,12 +94,15 @@ uirc_tokenizer_message(const char* str) short i; for (i = 0; i < IRC_MAXARGS && *p;) { if (i == IRC_MAXARGS - 1 || *p == ':') { - if (*p == ':') p++; + if (*p == ':') { + p++; + // trailing is supposed to represent a early ending of spaced arguments, not if there's any arguments at all + m->trailing = true; + } if ((m->args[i++] = malloc_string(p, strlen(p))) == NULL) { free_ctx(m, ws); return NULL; } - m->trailing = true; break; } else { const char* tmp = NULL; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..d5f43ce --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.16) + +include_directories(${uIRC_SOURCE_DIR}/include) +add_executable(fullloop fullloop.c common.c) +target_link_libraries(fullloop uirc) diff --git a/tests/common.c b/tests/common.c new file mode 100644 index 0000000..0792bc9 --- /dev/null +++ b/tests/common.c @@ -0,0 +1,82 @@ +/* + * This file is part of uIRC. (https://git.redxen.eu/caskd/uIRC) + * Copyright (c) 2019, 2020 Alex-David Denes + * + * uIRC is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * uIRC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with uIRC. If not, see . + */ + +#include "common.h" + +#include "uirc.h" // IRC_Message + +#include // printf() +#include // exit() +#include // strcmp() + +#define DELIMITER "-------------\n" +#define PRINTVARORNULL(VAR) ((VAR != NULL) ? VAR : "NULL") + +void +expect(const char* var, const char* exp) +{ + if (strcmp(var, exp) != 0) { + printf("Expected \"%s\" but got \"%s\" instead.\n", exp, var); + exit(EXIT_FAILURE); + } +} + +void +print_irc_message(const IRC_Message* ptr) +{ +#ifdef UIRC_FEATURE_IRCV3 + printf(DELIMITER); + + llist_t* tmp = ptr->tag_list; + for (; tmp != NULL; tmp = tmp->next) { print_irc_tag((IRC_Tag*) tmp->content); } +#endif /* UIRC_FEATURE_IRCV3 */ + + if (ptr->source != NULL) { print_irc_user(ptr->source); } + + if (ptr->command != NULL) printf("mc: %s\n", ptr->command); + + for (short i = 0; i < IRC_MAXARGS && ptr->args[i] != NULL; i++) { printf("marg[%i]: %s\n", i, ptr->args[i]); } + + printf(DELIMITER); +} + +#ifdef UIRC_FEATURE_IRCV3 +void +print_irc_tag(const IRC_Tag* tag) +{ + printf(DELIMITER "tn: %s\n" + "tv: %s\n" + "tc: %i\n" DELIMITER, + PRINTVARORNULL(tag->key), + PRINTVARORNULL(tag->value), + tag->clientbound); +} +#endif /* UIRC_FEATURE_IRCV3 */ + +void +print_irc_user(const IRC_User* user) +{ + printf(DELIMITER "un: %s\n" + "uu: %s\n" + "ur: %s\n" + "uh: %s\n" DELIMITER, + PRINTVARORNULL(user->nick), + PRINTVARORNULL(user->user), + PRINTVARORNULL(user->real), + PRINTVARORNULL(user->host)); +} diff --git a/tests/common.h b/tests/common.h new file mode 100644 index 0000000..b39430b --- /dev/null +++ b/tests/common.h @@ -0,0 +1,40 @@ +/* + * This file is part of uIRC. (https://git.redxen.eu/caskd/uIRC) + * Copyright (c) 2019, 2020 Alex-David Denes + * + * uIRC is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * uIRC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with uIRC. If not, see . + */ + +#include "uirc.h" // IRC_* + +#include // printf() +#include // exit() +#include // strcmp() + +#ifndef UIRC_GUARD_PRIVATE_COMMONTESTS +#define UIRC_GUARD_PRIVATE_COMMONTESTS + +#define ERRIFNULL(VAR, ...) \ + if (VAR == NULL) fprintf(stderr, __VA_ARGS__) + +void expect(const char* var, const char* exp); + +void print_irc_message(const IRC_Message* ptr); +#ifdef UIRC_FEATURE_IRCV3 +void print_irc_tag(const IRC_Tag* tag); +#endif /* UIRC_FEATURE_IRCV3 */ +void print_irc_user(const IRC_User* user); + +#endif /* UIRC_GUARD_PRIVATE_COMMONTESTS */ + diff --git a/tests/fullloop.c b/tests/fullloop.c new file mode 100644 index 0000000..f17408b --- /dev/null +++ b/tests/fullloop.c @@ -0,0 +1,61 @@ +/* + * This file is part of uIRC. (https://git.redxen.eu/caskd/uIRC) + * Copyright (c) 2019, 2020 Alex-David Denes + * + * uIRC is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * uIRC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with uIRC. If not, see . + */ + +#include "common.h" // expect() print_irc_message() +#include "uirc.h" // uirc_* IRC_* + +#include // assert() +#include // printf() +#include // EXIT_SUCCESS +#include // strlen() + +int +main(void) +{ + const char* str[] = { ":buddy@hostnet.com PRIVMSG #general :login hunter12\r\n", + "NOTICE * :nice cock bro\r\n", + "@day=13;willtolive=0 PRIVMSG you :helo\r\n", + ":mom!mother@localhost NOTICE #netadmin :johnny, your pizza is done\r\n", + "NOTICE after 14 arguments the trailing starts early so there's no need for a colon hmm interesting really\r\n" }; + + for (unsigned long i = 0; i < sizeof(str) / sizeof(*str); i++) { + IRC_Message* m = NULL; + if ((m = uirc_tokenizer_message(str[i])) == NULL) { + fprintf(stderr, "Failed to convert string to IRC_Message struct.\n"); + return EXIT_FAILURE; + } + print_irc_message(m); + ssize_t ret; + char buf[513]; + if ((ret = uirc_assembler_message(buf, m, sizeof(buf) / sizeof(char))) <= 0) { + fprintf(stderr, "Failed to convert IRC_Message struct to a string. (%li)\n", ret); + return EXIT_FAILURE; + } + size_t s1 = strlen(str[i]), s2 = strlen(buf); + if (s1 != s2) { + fprintf(stderr, "String lenght doesn't match after parsing and assembly. Expected %li but got %li\n", s1, s2); + fprintf(stderr, "%s\n", buf); + return EXIT_FAILURE; + } + expect(buf, str[i]); + uirc_free_message(m); + free(m); + } + + return EXIT_SUCCESS; +}