more utils, more tests (not all tested yet)
authorNikiroo <niki@nikiroo.be>
Sun, 27 Feb 2022 22:36:03 +0000 (23:36 +0100)
committerNikiroo <niki@nikiroo.be>
Sun, 27 Feb 2022 22:36:03 +0000 (23:36 +0100)
15 files changed:
src/tests.d
src/tests/launcher.c
src/tests/launcher.h
src/tests/utils.d
src/tests/utils/array.c [new file with mode: 0644]
src/tests/utils/cstring.c [new file with mode: 0644]
src/tests/utils/main.c
src/tests/utils/main.h
src/tests/utils/test_readln.txt [new file with mode: 0644]
src/utils/base64.c [new file with mode: 0644]
src/utils/base64.h [new file with mode: 0644]
src/utils/cstring.c [new file with mode: 0644]
src/utils/cstring.h [new file with mode: 0644]
src/utils/net.c [new file with mode: 0644]
src/utils/net.h [new file with mode: 0644]

index d04a9658e234ccfb56561aa3a829f5b616c54129..252cc6662acf7a90b76c5ab9e7af37852fdd4e1a 100644 (file)
@@ -2,7 +2,6 @@
 
 CFLAGS   += -Wall -pedantic -I./ -I ../ -std=c99
 CXXFLAGS += -Wall -pedantic -I./ -I ../
-LDFLAGS  += -lcheck
 
 ifdef DEBUG
 CFLAGS   += -ggdb -O0
index fe34be1f502d953d2ab363d32d2943b1db48aea4..6d3135843b29dab002a2cfb4f62610a8647c7df1 100644 (file)
@@ -1,3 +1,22 @@
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2022 Niki Roo
+ *
+ * This program 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
+ * (at your option) any later version.
+ *
+ * This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
 #include <check.h>
 #include <string.h>
 #include <stdio.h>
index 3d2825003a512fffe9681147cbe010178bec63b7..750a8422c64b66ec8db62f9fa7365f32580ba3b7 100644 (file)
@@ -15,19 +15,19 @@ END_TEST\
 #define FAIL(...) \
 ck_abort_msg(__VA_ARGS__)\
 
-#define ASSERT_EQUALS_STR(title, un, deux) \
-       if (strcmp(un, deux)) { \
-ck_abort_msg("%s\n\tExpected: <%s>\n\tReceived: <%s>", title, un, deux); \
+#define ASSERT_EQUALS_STR(title, expected, received) \
+       if (strcmp(expected, received)) { \
+ck_abort_msg("%s\n\tExpected: <%s>\n\tReceived: <%s>", title, expected, received); \
 }
 
-#define ASSERT_EQUALS_INT(title, un, deux) \
-       if (un != deux) { \
-ck_abort_msg("%s\n\tExpected: %d\n\tReceived: %d", title, un, deux); \
+#define ASSERT_EQUALS_INT(title, expected, received) \
+       if (expected != received) { \
+ck_abort_msg("%s\n\tExpected: %d\n\tReceived: %d", title, expected, received); \
 }
 
-#define ASSERT_EQUALS_SIZE(title, un, deux) \
-       if (un != deux) { \
-ck_abort_msg("%s\n\tExpected: %zu\n\tReceived: %zu", title, un, deux); \
+#define ASSERT_EQUALS_SIZE(title, expected, received) \
+       if (expected != received) { \
+ck_abort_msg("%s\n\tExpected: %zu\n\tReceived: %zu", title, expected, received); \
 }
 
 SRunner *get_tests(int more);
index 1a530fc49cd8f455f3e132d0fe735d5b76b4b9f8..6ab01fcec8870d829e0d61f260b0052d052d046e 100644 (file)
@@ -3,6 +3,9 @@
 CFLAGS   += -Wall -pedantic -I./ -I ../ -std=c99
 CXXFLAGS += -Wall -pedantic -I./ -I ../
 LDFLAGS  += -lcheck
+ifdef DEBIAN
+LDFLAGS  += -lsubunit -lm -lpthread -lrt
+endif
 
 ifdef DEBUG
 CFLAGS   += -ggdb -O0
diff --git a/src/tests/utils/array.c b/src/tests/utils/array.c
new file mode 100644 (file)
index 0000000..956d04d
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2022 Niki Roo
+ *
+ * This program 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
+ * (at your option) any later version.
+ *
+ * This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "launcher.h"
+#include "utils/array.h"
+
+array *a;
+
+void test_array_setup() {
+       a = new_array(sizeof(char), 80);
+}
+
+void test_array_teardown() {
+       free_array(a);
+       a = NULL;
+}
+
+void array_reset() {
+       test_array_teardown();
+       test_array_setup();
+}
+
+START(init)
+               if (!a)
+                       FAIL("new_array returned NULL");
+
+               if (array_count(a))
+                       FAIL("empty array has a size of %zu", array_count(a));
+               END
+
+Suite *test_array(const char title[]) {
+       Suite *suite = suite_create(title);
+
+       TCase *core = tcase_create("core");
+       tcase_add_checked_fixture(core, test_array_setup, test_array_teardown);
+       tcase_add_test(core, init);
+
+       suite_add_tcase(suite, core);
+
+       return suite;
+}
+
+Suite *test_array_more(const char title[]) {
+       Suite *suite = suite_create(title);
+
+       TCase *tmore = tcase_create("more");
+       tcase_add_checked_fixture(tmore, test_array_setup, test_array_teardown);
+       // TODO
+
+       suite_add_tcase(suite, tmore);
+
+       return suite;
+}
diff --git a/src/tests/utils/cstring.c b/src/tests/utils/cstring.c
new file mode 100644 (file)
index 0000000..23e6bcb
--- /dev/null
@@ -0,0 +1,265 @@
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2022 Niki Roo
+ *
+ * This program 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
+ * (at your option) any later version.
+ *
+ * This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "launcher.h"
+#include "utils/cstring.h"
+
+#define TEST_FILE_READLN "utils/test_readln.txt"
+
+cstring *s;
+
+void test_cstring_setup() {
+       s = new_cstring();
+}
+
+void test_cstring_teardown() {
+       free_cstring(s);
+       s = NULL;
+}
+
+void cstring_reset() {
+       test_cstring_teardown();
+       test_cstring_setup();
+}
+
+START(init)
+               if (!s)
+                       FAIL("new_cstring returned NULL");
+
+               if (s->length)
+                       FAIL("empty cstring has a size of %zu", s->length);
+               END
+
+START(adds)
+               char *str;
+
+               str = "testy";
+               cstring_add(s, str);
+               ASSERT_EQUALS_STR("Short string", str, s->string);
+               cstring_reset();
+
+               str = "Fanfan entre dans un bar";
+               cstring_add(s, str);
+               ASSERT_EQUALS_STR("Medium string", str, s->string);
+               cstring_reset();
+
+               str = "Ligne 1\nLigne 2\nLigne 3, attention 4 = vide\n";
+               cstring_add(s, str);
+               ASSERT_EQUALS_STR("Multi-line", str, s->string);
+               cstring_reset();
+
+               str =
+                               "Les accents en français sont bien là et se retrouvent avec une fréquence élevée";
+               cstring_add(s, str);
+               ASSERT_EQUALS_STR("accents", str, s->string);
+               cstring_reset();
+
+               str = "cents: ¢, copyright: ©, arrows: →↓↑←";
+               cstring_add(s, str);
+               ASSERT_EQUALS_STR("UTF-8", str, s->string);
+               cstring_reset();
+
+               str = "Os iusti meditabitur sapientiam,\n"
+                               "Et lingua\n"
+                               "eius loquetur\n"
+                               "indicium.\n"
+                               "\n"
+                               "Beatus\n"
+                               "vir qui\n"
+                               "suffert tentationem, Quoniqm\n"
+                               "cum probates\n"
+                               "fuerit accipient\n"
+                               "coronam vitae\n"
+                               "\n"
+                               "Kyrie, fons bonitatis.\n"
+                               "Kyrie, ignis divine, eleison.\n"
+                               "\n"
+                               "O quam sancta, quam serena,\n"
+                               "Quam benigma, quam amoena esse Virgo creditur.\n"
+                               "O quam sancta, quam serena,\n"
+                               "Quam benigma, quam amoena,\n"
+                               "O castitatis lilium.\n"
+                               "\n"
+                               "Kyrie, fons bonitatis.\n"
+                               "Kyrie, ignis divine, eleison.\n"
+                               "\n"
+                               "O quam sancta, quam serena,\n"
+                               "Quam benigma, quam amoena,\n"
+                               "O castitatis lilium.\n";
+               cstring_add(s, str);
+               ASSERT_EQUALS_STR("Long, multi-line string", str, s->string);
+               cstring_reset();
+
+               END
+
+START(clear)
+               if (!s)
+                       FAIL("new_cstring returned NULL");
+
+               if (s->length)
+                       FAIL("empty cstring has a size of %zu", s->length);
+               END
+
+START(addp)
+               char *str;
+
+               cstring_addp(s, "%d", 42);
+               ASSERT_EQUALS_STR("Simple int", "42", s->string);
+               cstring_reset();
+
+               cstring_addp(s, "%02d", 1);
+               ASSERT_EQUALS_STR("Leading zero int", "01", s->string);
+               cstring_reset();
+
+               cstring_addp(s, "%d", 352646);
+               ASSERT_EQUALS_STR("Large int", "352646", s->string);
+               cstring_reset();
+
+               str = "Simple test string";
+               cstring_addp(s, "%s", str);
+               ASSERT_EQUALS_STR("Simple string", str, s->string);
+               cstring_reset();
+
+               cstring_addp(s, "%s", "String 1, ");
+               str = "String 2";
+               cstring_addp(s, "%s", "String 2");
+               ASSERT_EQUALS_STR("Cumulative strings", "String 1, String 2", s->string);
+               cstring_reset();
+
+               END
+
+START(readln)
+               int read;
+               FILE *testin = fopen(TEST_FILE_READLN, "r");
+               if (!testin)
+                       FAIL("Test file not found: test_readln.txt");
+
+               read = cstring_readline(s, testin);
+               if (!read)
+                       FAIL("first line should not be last");
+               ASSERT_EQUALS_STR("first line incorrect", "ligne 1", s->string);
+               cstring_reset();
+
+               read = cstring_readline(s, testin);
+               if (!read)
+                       FAIL("second line should not be last");
+               ASSERT_EQUALS_STR("second line incorrect", "", s->string);
+               cstring_reset();
+
+               read = cstring_readline(s, testin);
+               if (!read)
+                       FAIL("third line should not be last");
+               ASSERT_EQUALS_STR("third line incorrect", "ligne 3", s->string);
+               cstring_reset();
+
+               if (cstring_readline(s, testin)) {
+                       FAIL("fourth line should not exist");
+               }
+
+               END
+
+START(ends_with)
+               char *end;
+
+               cstring_add(s, "fichier.ext");
+
+               end = ".ext";
+               if (!cstring_ends_with(s, end))
+                       FAIL("fichier.ext does not end in %s", end);
+
+               end = ".ex";
+               if (cstring_ends_with(s, end))
+                       FAIL("fichier.ext ends in %s", end);
+
+               end = "aext";
+               if (cstring_ends_with(s, end))
+                       FAIL("fichier.ext does not end in %s", end);
+
+               end = "";
+               if (!cstring_ends_with(s, end))
+                       FAIL("fichier.ext does not end with nothing");
+
+               END
+
+START(starts_with)
+               char *start;
+
+               cstring_add(s, "fichier.ext");
+
+               start = "fichier";
+               if (!cstring_starts_with(s, start, 0))
+                       FAIL("fichier.ext does not start with %s", start);
+
+               start = "ichier";
+               if (cstring_starts_with(s, start, 0))
+                       FAIL("fichier.ext starts with %s", start);
+
+               start = "afichier";
+               if (cstring_starts_with(s, start, 0))
+                       FAIL("fichier.ext starts with %s", start);
+
+               start = "";
+               if (!cstring_starts_with(s, start, 0))
+                       FAIL("fichier.ext does not start with nothing");
+
+               start = "chier";
+               if (!cstring_starts_with(s, start, 2))
+                       FAIL("fichier.ext +2 does not start with %s", start);
+
+               END
+
+START(many_adds)
+               size_t count = 50000000;
+               for (size_t i = 0; i < count; i++) {
+                       cstring_add(s, "1234567890");
+               }
+
+               ASSERT_EQUALS_SIZE("Lot of adds", count * 10, s->length);
+
+               END
+
+Suite *test_cstring(const char title[]) {
+       Suite *suite = suite_create(title);
+
+       TCase *core = tcase_create("core");
+       tcase_add_checked_fixture(core, test_cstring_setup, test_cstring_teardown);
+       tcase_add_test(core, init);
+       tcase_add_test(core, adds);
+       tcase_add_test(core, clear);
+       tcase_add_test(core, addp);
+       tcase_add_test(core, readln);
+       tcase_add_test(core, ends_with);
+       tcase_add_test(core, starts_with);
+
+       suite_add_tcase(suite, core);
+
+       return suite;
+}
+
+Suite *test_cstring_more(const char title[]) {
+       Suite *suite = suite_create(title);
+
+       TCase *tmore = tcase_create("more");
+       tcase_add_checked_fixture(tmore, test_cstring_setup, test_cstring_teardown);
+       tcase_add_test(tmore, many_adds);
+
+       suite_add_tcase(suite, tmore);
+
+       return suite;
+}
index 6354957e34591232f8cbb0e3c42d5b618ccdcf09..182401d77342d9cc757e2d1e5b235283f4815fac 100644 (file)
 #include "main.h"
 #include "launcher.h"
 
+SRunner *runner = NULL;
+void add_test(Suite *test);
+
 SRunner *get_tests(int more) {
-       //TODO: add tests (the code for those is not ready yet)
-       /*
-       SRunner *runner = srunner_create(test_cstring("cstring"));
+       add_test(test_cstring("cstring"));
+       if (more)
+               add_test(test_cstring_more("cstring -- more (longer)"));
+
+       add_test(test_array("array"));
        if (more)
-               srunner_add_suite(runner, test_cstring_more("more tests (longer)"));
+               add_test(test_array_more("array -- more (longer)"));
 
        return runner;
-       */
-       return NULL;
+}
+
+void add_test(Suite *test) {
+       if (!runner) {
+               runner = srunner_create(test);
+       } else {
+               srunner_add_suite(runner, test);
+       }
 }
index fa1b559f086789a9f7ba30622d5e7e0d571c5289..497bc2f1c0d2bfb3e150eed59410619826267ad2 100644 (file)
@@ -22,8 +22,9 @@
 
 #include <check.h>
 
-//TODO: add tests (the code for those is not ready yet)
-//Suite *test_cstring(const char title[]);
-//Suite *test_cstring_more(const char title[]);
+Suite *test_cstring(const char title[]);
+Suite *test_cstring_more(const char title[]);
+Suite *test_array(const char title[]);
+Suite *test_array_more(const char title[]);
 
 #endif /* SRC_TESTS_UTILS_MAIN_H_ */
diff --git a/src/tests/utils/test_readln.txt b/src/tests/utils/test_readln.txt
new file mode 100644 (file)
index 0000000..10f2897
--- /dev/null
@@ -0,0 +1,3 @@
+ligne 1
+
+ligne 3
diff --git a/src/utils/base64.c b/src/utils/base64.c
new file mode 100644 (file)
index 0000000..cf6657c
--- /dev/null
@@ -0,0 +1,232 @@
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2013 Niki Roo
+ *
+ * This program 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
+ * (at your option) any later version.
+ *
+ * This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "base64.h"
+#include "cstring.h"
+
+cstring *base64_decodesi(base64 *self, char *data, size_t len);
+//cstring *base64_encodesi(base64 *self, char *data, size_t len);
+
+char *b64_encode(const char *data, const char *encoding_table,
+                       size_t input_length,
+                       size_t *output_length);
+
+char *b64_decode(const char *data, const char *decoding_table,
+                       size_t input_length,
+                       size_t *output_length);
+
+static char b64_encoding_table[] = {   'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
+                                       'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+                                       'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
+                                       'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
+                                       'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
+                                       'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+                                       'w', 'x', 'y', 'z', '0', '1', '2', '3',
+                                       '4', '5', '6', '7', '8', '9', '+', '/'};
+
+static int b64_mod_table[] = {0, 2, 1};
+
+base64 *base64_new() {
+       base64 *self;
+       int i;
+       
+       self = (base64 *)malloc(sizeof(base64));
+       self->table = malloc(256 * sizeof(char));
+       
+       for (i = 0; i < 64; i++)
+               self->table[(unsigned char)b64_encoding_table[i]] = i;
+       
+       return self;
+}
+
+void base64_free(base64 *self) {
+       if (!self)
+               return;
+       
+       free(self->table);
+       free(self);
+}
+
+cstring *base64_encode(base64 *self, cstring *data) {
+       if (!data)
+               return NULL;
+       
+       return base64_encodesi(self, data->string, data->length);
+}
+
+cstring *base64_decode(base64 *self, cstring *data) {
+       if (!data)
+               return NULL;
+       
+       return base64_decodesi(self, data->string, data->length);
+}
+
+cstring *base64_encodes(base64 *self, char *data) {
+       size_t len;
+       
+       if (!data)
+               return NULL;
+       
+       len = strlen(data);
+       
+       return base64_encodesi(self, data, len);
+}
+
+cstring *base64_decodes(base64 *self, char *data) {
+       size_t len;
+       
+       if (data)
+               len = strlen(data);
+       
+       return base64_decodesi(self, data, len);
+}
+
+cstring *base64_encodesi(base64 *self, char *data, size_t len) {
+       cstring *rep;
+       char *encoded;
+       size_t size;
+       
+       rep = NULL;
+       size = 0;
+       encoded = b64_encode(data, b64_encoding_table, len, &size);
+       
+       if (encoded) {
+               rep = new_cstring();
+               free(rep->string);
+               rep->string = encoded;
+               rep->length = size;
+               cstring_compact(rep);
+       }
+       
+       return rep;
+}
+
+cstring *base64_decodesi(base64 *self, char *data, size_t len) {
+       cstring *rep;
+       char *decoded;
+       size_t size;
+       
+       rep = NULL;
+       size = 0;
+       decoded = b64_decode(data, self->table, len, &size);
+       
+       if (decoded) {
+               rep = new_cstring();
+               free(rep->string);
+               rep->string = decoded;
+               rep->length = size;
+               cstring_compact(rep);
+       }
+       
+       return rep;
+}
+
+char *b64_encode(const char *data, const char *encoding_table,
+                       size_t input_length,
+                       size_t *output_length) {
+       unsigned int i, j;
+
+       *output_length = 4 * ((input_length + 2) / 3) + 1;
+
+       char *encoded_data = malloc(*output_length);
+       if (encoded_data == NULL)
+               return NULL;
+
+       for (i = 0, j = 0 ; i < input_length ; ) {
+               uint32_t octet_a = i < input_length ? 
+                       (unsigned char)data[i++] : 0;
+               uint32_t octet_b = i < input_length ?
+                       (unsigned char)data[i++] : 0;
+               uint32_t octet_c = i < input_length ?
+                       (unsigned char)data[i++] : 0;
+
+               uint32_t triple = (octet_a << 0x10) 
+                               + (octet_b << 0x08) 
+                               +  octet_c;
+
+               encoded_data[j++] = 
+                       (char)encoding_table[(triple >> 3 * 6) & 0x3F];
+               encoded_data[j++] =
+                       (char)encoding_table[(triple >> 2 * 6) & 0x3F];
+               encoded_data[j++] =
+                       (char)encoding_table[(triple >> 1 * 6) & 0x3F];
+               encoded_data[j++] =
+                       (char)encoding_table[(triple >> 0 * 6) & 0x3F];
+       }
+
+       for (i = 0 ; (int)i < b64_mod_table[input_length % 3] ; i++)
+               encoded_data[*output_length - 2 - i] = '=';
+       encoded_data[*output_length - 1] = '\0';
+
+       return encoded_data;
+}
+
+
+char *b64_decode(const char *data, const char *decoding_table,
+                       size_t input_length,
+                       size_t *output_length) {
+       
+       unsigned int i, j;
+
+       if (input_length % 4 != 0)
+               return NULL;
+
+       *output_length = (input_length / 4 * 3) + 1;
+       if (data[input_length - 2] == '=') (*output_length)--;
+       if (data[input_length - 3] == '=') (*output_length)--;
+
+       char *decoded_data = malloc(*output_length);
+       if (decoded_data == NULL)
+               return NULL;
+
+       i = j = 0;
+       for ( ; i < input_length ; i += 4) {
+               uint32_t sextet_a = 0;
+               uint32_t sextet_b = 0;
+               uint32_t sextet_c = 0;
+               uint32_t sextet_d = 0;
+               
+               if (data[i] != '=') {
+                       sextet_a = decoding_table[(unsigned char)data[i + 0]];
+                       sextet_b = decoding_table[(unsigned char)data[i + 1]];
+                       sextet_c = decoding_table[(unsigned char)data[i + 2]];
+                       sextet_d = decoding_table[(unsigned char)data[i + 3]];
+               }
+               
+               uint32_t triple = (sextet_a << 3 * 6)
+                               + (sextet_b << 2 * 6)
+                               + (sextet_c << 1 * 6)
+                               + (sextet_d << 0 * 6);
+
+               if (j + 1 < *output_length)
+                       decoded_data[j++] = (char)((triple >> 2 * 8) & 0xFF);
+               if (j + 1 < *output_length)
+                       decoded_data[j++] = (char)((triple >> 1 * 8) & 0xFF);
+               if (j + 1  < *output_length)
+                       decoded_data[j++] = (char)((triple >> 0 * 8) & 0xFF);
+       }
+
+       decoded_data[*output_length - 1] = '\0';
+       return decoded_data;
+}
+
diff --git a/src/utils/base64.h b/src/utils/base64.h
new file mode 100644 (file)
index 0000000..7bd9afe
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2013 Niki Roo
+ *
+ * This program 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
+ * (at your option) any later version.
+ *
+ * This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef BASE64_H
+#define BASE64_H
+
+#include <stdlib.h>
+#include "cstring.h"
+
+typedef struct {
+       char *table;
+} base64;
+
+/**
+ * Create a new base64 codec.
+ *
+ * @return a new codec
+ */
+base64 *base64_new();
+
+/**
+ * Free the given code.
+ */
+void base64_free(base64 *self);
+
+cstring *base64_encode(base64 *self, cstring *data);
+
+cstring *base64_decode(base64 *self, cstring *data);
+
+cstring *base64_encodes(base64 *self, char *data);
+
+cstring *base64_encodesi(base64 *self, char *data, size_t size);
+
+cstring *base64_decodes(base64 *self, char *data);
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/src/utils/cstring.c b/src/utils/cstring.c
new file mode 100644 (file)
index 0000000..b0dda84
--- /dev/null
@@ -0,0 +1,730 @@
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2013 Niki Roo
+ *
+ * This program 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
+ * (at your option) any later version.
+ *
+ * This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*
+ Name:        cstring.c
+ Copyright:   niki (cc-by-nc) 2011
+ Author:      niki
+ Date:        2011-06-16
+ Description: cstring is a collection of helper functions to manipulate string of text
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <wchar.h>
+#include <wctype.h>
+#include <stddef.h>
+#include <stdarg.h>
+
+/* Windows (and maybe others?) doesn't know about strnlen */
+#ifndef strnlen
+size_t strnlen(const char *s, size_t maxlen);
+size_t strnlen(const char *s, size_t maxlen) {
+       size_t i;
+       for (i = 0; s[i]; i++) {
+               if (i >= maxlen)
+                       return maxlen;
+       }
+
+       return i;
+}
+#endif
+
+#include "cstring.h"
+#include "net.h"
+#include "base64.h"
+
+#ifndef BUFFER_SIZE
+#define BUFFER_SIZE 81
+#endif
+
+#ifdef WIN32
+#define CSTRING_SEP '\\'
+#else
+#define CSTRING_SEP '/'
+#endif
+
+//start of private prototypes
+
+struct cstring_private_struct {
+       size_t buffer_length;
+};
+
+void cstring_change_case(cstring *self, int up);
+
+//end of private prototypes
+
+cstring *new_cstring() {
+       cstring *string;
+
+       string = malloc(sizeof(cstring));
+       strcpy(string->CNAME, "[CString]");
+       string->priv = malloc(sizeof(struct cstring_private_struct));
+       string->length = 0;
+       string->priv->buffer_length = BUFFER_SIZE;
+       string->string = malloc(sizeof(char) * BUFFER_SIZE);
+       string->string[0] = '\0';
+
+       return string;
+}
+
+void free_cstring(cstring *string) {
+       if (!string)
+               return;
+
+       free(string->priv);
+       free(string->string);
+
+       free(string);
+}
+
+void cstring_grow(cstring *self, int min) {
+       cstring_grow_to(self, self->length + min);
+}
+
+void cstring_grow_to(cstring *self, int buffer) {
+       if (buffer > self->priv->buffer_length) {
+               self->priv->buffer_length = buffer;
+               self->string = (char *) realloc(self->string,
+                               sizeof(char) * self->priv->buffer_length);
+       }
+}
+
+char *cstring_convert(cstring *self) {
+       char *string;
+
+       if (!self)
+               return NULL;
+
+       // Note: this could be skipped.
+       cstring_compact(self);
+
+       free(self->priv);
+       string = (self->string);
+       free(self);
+
+       return string;
+}
+
+cstring *cstring_clone(cstring *self) {
+       if (self == NULL)
+               return NULL;
+
+       return cstring_clones(self->string);
+}
+
+char *cstring_sclone(cstring *self) {
+       if (self == NULL)
+               return NULL;
+
+       return cstring_convert(cstring_clone(self));
+}
+
+char *cstring_sclones(const char self[]) {
+       return cstring_convert(cstring_clones(self));
+}
+
+cstring *cstring_clones(const char self[]) {
+       cstring *clone;
+
+       if (self == NULL)
+               return NULL;
+
+       clone = new_cstring();
+       cstring_add(clone, self);
+
+       return clone;
+}
+
+void cstring_compact(cstring *self) {
+       if (self != NULL) {
+               self->priv->buffer_length = self->length + 1;
+               self->string = (char *) realloc(self->string, self->length + 1);
+       }
+}
+
+void cstring_cut_at(cstring *self, size_t size) {
+       if (self->length > size) {
+               self->string[size] = '\0';
+               self->length = size;
+       }
+}
+
+/*
+ clist *cstring_splitc(cstring *self, char delim, char quote) {
+ clist *list;
+ cstring *d, *q;
+
+ d = new_cstring();
+ q = new_cstring();
+
+ if (delim)
+ cstring_add_car(d, delim);
+ if (quote)
+ cstring_add_car(q, quote);
+
+ list = cstring_split(self, d, q);
+
+ free_cstring(d);
+ free_cstring(q);
+
+ return list;
+ }
+
+ clist *cstring_splits(cstring *self, const char delim[], const char quote[]) {
+ clist *list;
+ cstring *d, *q;
+
+ d = new_cstring();
+ q = new_cstring();
+
+ if (delim)
+ cstring_add(d, delim);
+ if (quote)
+ cstring_add(q, quote);
+
+ list = cstring_split(self, d, q);
+
+ free_cstring(d);
+ free_cstring(q);
+
+ return list;
+ }
+ clist *cstring_split(cstring *self, cstring *delim, cstring *quote) {
+ clist *list;
+ cstring *elem;
+ clist_node *node;
+ size_t i;
+ int in_quote;
+ int hasdelim;
+
+ hasdelim = delim && delim->length > 0;
+
+ list = clist_new();
+ in_quote = 0;
+ elem = NULL;
+ for (i = 0; i < self->length; i++) {
+ if (quote->length > 0 && cstring_starts_with(self, quote, i)) {
+ in_quote = !in_quote;
+ i += quote->length - 1;
+ } else {
+ if (elem == NULL) {
+ elem = new_cstring();
+ node = clist_node_new();
+ node->data = elem;
+ node->free_data = free_cstring;
+ clist_add(list, node);
+ }
+ if (!in_quote && hasdelim && cstring_starts_with(self, delim, i)) {
+ elem = new_cstring();
+ node = clist_node_new();
+ node->data = elem;
+ node->free_data = free_cstring;
+ clist_add(list, node);
+ i += delim->length - 1;
+ } else {
+ cstring_add_car(elem, self->string[i]);
+ }
+ }
+ }
+
+ return list;
+ }
+ */
+
+cstring *cstring_substring(cstring *self, size_t start, size_t length) {
+       cstring *sub;
+       char *source;
+
+       if (length == 0) {
+               length = self->length - start;
+       }
+
+       sub = new_cstring();
+       source = self->string;
+       source = source + start;
+
+       cstring_addn(sub, source, length);
+
+       return sub;
+}
+
+int cstring_starts_with(cstring *self, const char find[], size_t start_index) {
+       return cstring_sstarts_with(self->string, find, start_index);
+}
+
+int cstring_sstarts_with(const char string[], const char find[],
+               size_t start_index) {
+       size_t i;
+
+       for (i = 0;
+                       string[start_index + i] == find[i]
+                                       && string[start_index + i] != '\0' && find[i] != '\0'; i++)
+               ;
+
+       return find[i] == '\0';
+}
+
+int cstring_ends_with(cstring *self, const char find[]) {
+       size_t sz_needle = strlen(find);
+       if (sz_needle <= self->length) {
+               if (!strcmp(self->string + (self->length - sz_needle), find))
+                       return 1;
+       }
+
+       return 0;
+}
+
+int cstring_sends_with(const char self[], const char find[]) {
+       size_t sz = strlen(self);
+       size_t sz_needle = strlen(find);
+       if (sz_needle <= sz) {
+               if (!strcmp(self + (sz - sz_needle), find))
+                       return 1;
+       }
+
+       return 0;
+}
+
+long cstring_find(const char self[], const char find[], size_t start_index) {
+       size_t sz = strlen(self);
+       if (sz > start_index) {
+               char *found = strstr(self + start_index, find);
+               if (found) {
+                       return (long) (found - self);
+               }
+       }
+
+       return -1;
+}
+
+long cstring_rfind(char self[], const char find[], size_t rstart_index) {
+       size_t sz = strlen(self);
+       size_t sz_needle = strlen(find);
+
+       if (rstart_index <= 0)
+               rstart_index += (sz - 1);
+
+       if (sz > rstart_index && sz_needle <= sz) {
+               for (size_t i = sz - sz_needle; i; i--) {
+                       if (cstring_sstarts_with(self, find, i))
+                               return i;
+               }
+       }
+
+       return -1;
+}
+
+int cstring_replace_car(cstring *self, char from, char to) {
+       size_t i;
+       int occur = 0;
+
+       for (i = 0; i < self->length; i++) {
+               if (self->string[i] == from) {
+                       self->string[i] = to;
+                       occur++;
+               }
+       }
+
+       return occur;
+}
+
+int cstring_replace(cstring *self, const char from[], const char to[]) {
+       cstring *buffer;
+       size_t i;
+       size_t step;
+       char *swap;
+       int occur;
+
+       // easy optimization:
+       if (from && to && from[0] && to[0] && !from[1] && !to[1])
+               return cstring_replace_car(self, from[0], to[0]);
+
+       // optimize for same-size strings?
+
+       step = strlen(from) - 1;
+       buffer = new_cstring();
+       occur = 0;
+       for (i = 0; i < self->length; i++) {
+               if (cstring_starts_with(self, from, i)) {
+                       cstring_add(buffer, to);
+                       i += step;
+                       occur++;
+               } else {
+                       cstring_add_car(buffer, self->string[i]);
+               }
+       }
+
+       // not clean, but quicker:
+       swap = self->string;
+       self->string = buffer->string;
+       buffer->string = swap;
+       self->length = buffer->length;
+
+       free_cstring(buffer);
+       return occur;
+}
+
+void cstring_clear(cstring *self) {
+       self->length = 0;
+       self->string[0] = '\0';
+}
+
+void cstring_reverse(cstring *self) {
+       size_t i;
+       size_t last;
+       char tmp;
+
+       if (self->length > 0) {
+               last = self->length - 1;
+               for (i = 0; i <= (last / 2); i++) {
+                       tmp = self->string[i];
+                       self->string[i] = self->string[last - i];
+                       self->string[last - i] = tmp;
+               }
+       }
+}
+
+void cstring_add_car(cstring *self, char source) {
+       char source2[2];
+
+       source2[0] = source;
+       source2[1] = '\0';
+
+       cstring_add(self, source2);
+}
+
+void cstring_addp(cstring *self, const char *fmt, ...) {
+       va_list ap;
+       char empty = '\0';
+
+       va_start(ap, fmt);
+       int sz = vsnprintf(&empty, 0, fmt, ap);
+       va_end(ap);
+
+       char *tmp = malloc((sz + 1) * sizeof(char));
+       cstring_grow(self, sz);
+
+       va_start(ap, fmt);
+       sz = vsnprintf(tmp, sz + 1, fmt, ap);
+       va_end(ap);
+
+       cstring_add(self, tmp);
+
+       free(tmp);
+}
+
+void cstring_add(cstring *self, const char source[]) {
+       cstring_addf(self, source, 0);
+}
+
+void cstring_addf(cstring *self, const char source[], size_t indexi) {
+       size_t ss, ptr;
+
+       if (source != NULL && strlen(source) > indexi) {
+               ss = strlen(source) - indexi;
+               while ((self->length + ss) >= (self->priv->buffer_length)) {
+                       self->priv->buffer_length += BUFFER_SIZE;
+               }
+               self->string = (char *) realloc(self->string,
+                               sizeof(char) * self->priv->buffer_length);
+
+               for (ptr = self->length; ptr <= (self->length + ss); ptr++) {
+                       self->string[ptr] = source[ptr - self->length + indexi];
+               }
+               self->length += ss;
+       }
+}
+
+void cstring_addn(cstring *self, const char source[], size_t n) {
+       cstring_addfn(self, source, 0, n);
+}
+
+void cstring_addfn(cstring *self, const char source[], size_t indexi, size_t n) {
+       size_t i;
+       char *tmp;
+
+       for (i = indexi; i < (n + indexi) && source[i] != '\0'; i++)
+               ;
+       if (source[i] == '\0') {
+               cstring_addf(self, source, indexi);
+       } else {
+               tmp = (char *) malloc(sizeof(char) * (n + 1));
+               strncpy(tmp, source + indexi, n);
+               tmp[n] = '\0';
+               cstring_add(self, tmp);
+               free(tmp);
+       }
+}
+
+void cstring_rtrim(cstring *self, char car) {
+       for (size_t i = self->length - 1; i >= 0; i--) {
+               if (self->string[i] != car)
+                       break;
+               self->string[i] = '\0';
+               self->length--;
+       }
+}
+
+void cstring_trim(cstring *self, char car) {
+       if (car == '\0')
+               return;
+
+       cstring_rtrim(self, car);
+
+       int i = 0;
+       while (self->string[i] == car)
+               i++;
+
+       if (i) {
+               cstring *tmp = new_cstring();
+               cstring_add(tmp, self->string + i);
+
+               free(self->string);
+               self->priv->buffer_length = tmp->priv->buffer_length;
+               self->string = tmp->string;
+               tmp->string = NULL;
+               free_cstring(tmp);
+       }
+}
+
+size_t cstring_remove_crlf(cstring *self) {
+       size_t removed;
+
+       removed = cstring_sremove_crlf(self->string, self->length);
+       self->length -= removed;
+
+       return removed;
+}
+
+size_t cstring_sremove_crlf(char data[], size_t n) {
+       size_t removed;
+
+       removed = n;
+       while (removed > 0
+                       && (data[removed - 1] == '\r' || data[removed - 1] == '\n')) {
+               removed--;
+       }
+
+       data[removed] = '\0';
+
+       return removed;
+}
+
+void cstring_toupper(cstring *self) {
+       cstring_change_case(self, 1);
+}
+
+void cstring_tolower(cstring *self) {
+       cstring_change_case(self, 0);
+}
+
+void cstring_change_case(cstring *self, int up) {
+       wchar_t *wide;
+       char tmp[10];
+       const char *src = self->string;
+       size_t s, i;
+       mbstate_t state;
+
+// init the state (passing NULL is not thread-safe)
+       memset(&state, '\0', sizeof(mbstate_t));
+
+// won't contain MORE chars (but maybe less)
+       wide = (wchar_t *) malloc((self->length + 1) * sizeof(wchar_t));
+       s = mbsrtowcs(wide, &src, self->length, &state);
+       wide[s] = (wchar_t) '\0';
+       cstring_clear(self);
+       for (i = 0; i <= s; i++) {
+               if (up)
+                       wide[i] = (wchar_t) towupper((wint_t) wide[i]);
+               else
+                       wide[i] = (wchar_t) towlower((wint_t) wide[i]);
+               memset(&state, '\0', sizeof(mbstate_t));
+               wcrtomb(tmp, wide[i], &state);
+               cstring_add(self, tmp);
+       }
+       free(wide);
+}
+
+int cstring_readline(cstring *self, FILE *file) {
+       char buffer[BUFFER_SIZE];
+       size_t size = 0;
+       int full_line;
+
+// sanity check:
+       if (!file)
+               return 0;
+
+       buffer[BUFFER_SIZE - 1] = '\0'; // just in case
+
+       if (!feof(file)) {
+               cstring_clear(self);
+               buffer[0] = '\0';
+
+               // Note: strlen() could return 0 if the file contains \0
+               // at the start of a line
+               if (!fgets(buffer, (int) BUFFER_SIZE - 1, file))
+                       return 0;
+               size = strlen(buffer);
+
+               full_line = ((file && feof(file)) || size == 0
+                               || buffer[size - 1] == '\n');
+               size -= cstring_sremove_crlf(buffer, size);
+               cstring_add(self, buffer);
+
+               // No luck, we need to continue getting data
+               while (!full_line) {
+                       if (!fgets(buffer, (int) BUFFER_SIZE - 1, file))
+                               break;
+                       size = strlen(buffer);
+
+                       full_line = ((file && feof(file)) || size == 0
+                                       || buffer[size - 1] == '\n');
+                       size -= cstring_sremove_crlf(buffer, size);
+                       cstring_add(self, buffer);
+               }
+
+               return 1;
+       }
+
+       return 0;
+}
+
+void cstring_add_path(cstring *self, const char subpath[]) {
+       cstring_add_car(self, CSTRING_SEP);
+       cstring_add(self, subpath);
+}
+
+int cstring_pop_path(cstring *self, int how_many) {
+       char sep[] = { CSTRING_SEP };
+       int count = 0;
+
+       cstring_rtrim(self, CSTRING_SEP);
+       for (int i = 0; i < how_many; i++) {
+               size_t idx = cstring_rfind(self->string, sep, 0);
+               if (!idx)
+                       break;
+
+               cstring_cut_at(self, idx - 1);
+               count++;
+       }
+
+       return count;
+}
+
+cstring *cstring_getdir(const char path[]) {
+       cstring *result;
+       size_t i;
+
+       size_t sz = strlen(path);
+
+       i = sz - 1;
+       if (i >= 0 && path[i] == CSTRING_SEP)
+               i--;
+       for (; i >= 0 && path[i] != CSTRING_SEP; i--)
+               ;
+
+       if (i < 0)
+               return new_cstring();
+
+       result = new_cstring();
+       cstring_addn(result, path, i);
+
+       return result;
+}
+
+cstring *cstring_getfile(cstring *path) {
+       cstring *result;
+       ssize_t i;
+
+       i = (ssize_t) path->length - 1;
+       if (i >= 0 && path->string[i] == CSTRING_SEP)
+               i--;
+       for (; i >= 0 && path->string[i] != CSTRING_SEP; i--)
+               ;
+
+       if (i < 0 || (size_t) (i + 1) >= path->length)
+               return new_cstring();
+
+       result = cstring_clones(path->string + i + 1);
+       return result;
+}
+
+cstring *cstring_getfiles(const char path[]) {
+       cstring *copy, *result;
+
+       copy = cstring_clones(path);
+       result = cstring_getfile(copy);
+       free_cstring(copy);
+       return result;
+}
+
+cstring *cstring_to_b64(cstring *self) {
+       static base64 *cstring_b64 = NULL;
+       if (!cstring_b64)
+               cstring_b64 = base64_new();
+
+       return base64_encode(cstring_b64, self);
+}
+
+char *cstring_to_sb64(cstring *self) {
+       static base64 *cstring_b64 = NULL;
+       if (!cstring_b64)
+               cstring_b64 = base64_new();
+
+       return cstring_convert(base64_encode(cstring_b64, self));
+}
+
+char *cstring_to_sb64s(char *self, size_t size) {
+       static base64 *cstring_b64 = NULL;
+       if (!cstring_b64)
+               cstring_b64 = base64_new();
+
+       return cstring_convert(base64_encodesi(cstring_b64, self, size));
+}
+
+cstring *cstring_from_b64(cstring *self) {
+       static base64 *cstring_b64 = NULL;
+       if (!cstring_b64)
+               cstring_b64 = base64_new();
+
+       return base64_decode(cstring_b64, self);
+}
+
+char *cstring_from_sb64(cstring *self) {
+       static base64 *cstring_b64 = NULL;
+       if (!cstring_b64)
+               cstring_b64 = base64_new();
+
+       return cstring_convert(base64_decode(cstring_b64, self));
+}
+
+char *cstring_from_sb64s(char *self) {
+       static base64 *cstring_b64 = NULL;
+       if (!cstring_b64)
+               cstring_b64 = base64_new();
+
+       return cstring_convert(base64_decodes(cstring_b64, self));
+}
+
+int cstring_is_whole(cstring *self) {
+       return mbstowcs(NULL, self->string, 0) != (size_t) -1;
+}
diff --git a/src/utils/cstring.h b/src/utils/cstring.h
new file mode 100644 (file)
index 0000000..60dc5b1
--- /dev/null
@@ -0,0 +1,535 @@
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2013 Niki Roo
+ *
+ * This program 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
+ * (at your option) any later version.
+ *
+ * This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef CSTRING_H
+#define CSTRING_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+
+/**
+ * This is a cstring. It contains a suite of characters terminated by a NUL byte
+ * (or just a NUL byte), and a length.
+ * It is advised NOT to modify either of those directly.
+ * You can use cstring_convert to get a char*, though (in this case, the cstring
+ * MUST NOT be used again, and you are responsible for freeing the said char*).
+ */
+typedef struct cstring_struct cstring;
+typedef struct cstring_private_struct cstring_private;
+
+struct cstring_struct {
+       char CNAME[32];
+       char *string;
+       size_t length;
+       cstring_private *priv;
+};
+
+/**
+ * Instantiate a new cstring.
+ *
+ * Create (and allocate the memory for) a new cstring.
+ * Do not forget to call cstring_free(cstring) when done.
+ */
+cstring *new_cstring();
+
+/**
+ * Free the given cstring.
+ *
+ * Free all the resources allocated for this cstring.
+ *
+ * @param string the cstring to free, which MUST NOT be used again afterward
+ */
+void free_cstring(cstring *string);
+
+//TODO: desc: will make sure it has enough space for MIN more chars
+void cstring_grow(cstring *self, int min);
+
+//TODO: desc: will make sure it has enough space BUFFER chars (including NULL)
+void cstring_grow_to(cstring *self, int buffer);
+
+/**
+ * Add a char at the end of the given cstring.
+ *
+ * @param self the cstring to work on
+ * @param source the character to add
+ */
+void cstring_add_car(cstring *self, char source);
+
+/**
+ * Add a string (a sequence of char that MUST end with '\0') at the end of the given cstring.
+ *
+ * @param self the cstring to work on
+ * @param source the string to add
+ */
+void cstring_add(cstring *self, const char source[]);
+
+/**
+ * Add a string (a sequence of char that MUST end with '\0') at the end of the given cstring, starting from index.
+ *
+ * @param self the cstring to work on
+ * @param source the string to add
+ * @param index the starting index at which to copy from the source
+ */
+void cstring_addf(cstring *self, const char source[], size_t index);
+
+/**
+ * Add a string (a sequence of char that MAY end with '\0') at the end of the given cstring, up to N chars long.
+ *
+ * @param self the cstring to work on
+ * @param source the string to add
+ * @param n the maximum number of chars to add (excluding the NUL byte)
+ */
+void cstring_addn(cstring *self, const char source[], size_t n);
+
+/**
+ * Add a string (a sequence of char that MAY end with '\0') at the end of the given cstring, starting from index, up to N chars long.
+ *
+ * @param self the cstring to work on
+ * @param source the string to add
+ * @param index the starting index at which to copy from the source
+ * @param n the maximum number of chars to add (excluding the NUL byte)
+ */
+void cstring_addfn(cstring *self, const char source[], size_t index, size_t n);
+
+//TODO: desc + tests
+void cstring_addp(cstring *self, const char *fmt, ...);
+
+/**
+ * Cut the cstring at the given size if it is greater.
+ * E.g.: it will have (at most) this many characters (without counting NUL) in it after.
+ *
+ * @param self the cstring to work on
+ * @param size the size to cut at (the maximum size of the cstring after this operation, NUL excepted)
+ */
+void cstring_cut_at(cstring *self, size_t size);
+
+/**
+ * Create a substring of this cstring.
+ *
+ * @param self the cstring to work on
+ * @param start the index to start at
+ * @param length the number of characters to copy, 0 for 'up to the end'
+ *
+ * @return a newly allocated cstring
+ */
+cstring *cstring_substring(cstring *self, size_t start, size_t length);
+
+/**
+ * Split a cstring into "smaller" cstrings every time the given separator is found.
+ * Will also allow empty fields, ie: "a,b,,c" will return 4 cstrings, the third being empty).
+ *
+ * @param self the cstring to work on
+ * @param delim the separator, which can be longer than one character
+ *
+ * @return a list of cstring
+ */
+//TODO: use a []
+//clist *cstring_split(cstring *self, cstring *delim, cstring *quote);
+/**
+ * Split a cstring into "smaller" cstrings every time the given separator (which MUST end in \0) is found.
+ * Will also allow empty fields, ie: "a,b,,c" will return 4 cstrings, the third being empty).
+ *
+ * @param self the cstring to work on
+ * @param delim the separator, which can be longer than one character and MUST end with \0
+ *
+ * @return a list of cstring
+ */
+//TODO: use a []
+//clist *cstring_splits(cstring *self, const char delim[], const char quote[]);
+/**
+ * Split a cstring into "smaller" cstrings every time the given separator is found.
+ * Will also allow empty fields, ie: "a,b,,c" will return 4 cstrings, the third being empty).
+ *
+ * @param self the cstring to work on
+ * @param delim the separator
+ *
+ * @return a list of cstring
+ */
+//TODO: use a []
+//clist *cstring_splitc(cstring *self, char delim, char quote);
+/**
+ * Reverse the given cstring.
+ *
+ * @param self the cstring to work on
+ */
+void cstring_reverse(cstring *self);
+
+/**
+ * Replace all occurences of a string inside the given cstring by another.
+ *
+ * @param self the cstring to work on
+ * @param from the string to replace
+ * @param to the replacement string
+ *
+ * @return the number of occurences changed
+ */
+int cstring_replace(cstring *self, const char from[], const char to[]);
+
+/**
+ * Replace all occurences of a char inside the given cstring by another.
+ *
+ * @param self the cstring to work on
+ * @param from the char to replace
+ * @param to the replacement char
+ *
+ * @return the number of occurences changed
+ */
+int cstring_replace_car(cstring *self, char from, char to);
+
+/**
+ * Check if the cstring starts with the given pattern.
+ *
+ * @param self the cstring to work on
+ * @param find the string to find
+ * @param start_index the index at which to start the comparison
+ *
+ * @return 1 if it does
+ */
+int cstring_starts_with(cstring *self, const char find[], size_t start_index);
+
+/**
+ * Check if the string starts with the given pattern.
+ *
+ * @param self the string to work on
+ * @param find the string to find
+ * @param start_index the index at which to start the comparison
+ *
+ * @return 1 if it does
+ */
+int cstring_sstarts_with(const char string[], const char find[],
+               size_t start_index);
+
+/**
+ * Check if the cstring ends with the given pattern.
+ *
+ * @param self the cstring to work on
+ * @param find the string to find (if empty, will always be found)
+ * @param start_index the index at which to start the comparison
+ *
+ * @return 1 if it does
+ */
+int cstring_ends_with(cstring *self, const char find[]);
+
+/**
+ * Check if the string ends with the given pattern.
+ *
+ * @param self the string to work on
+ * @param find the string to find (if empty, will always be found)
+ * @param start_index the index at which to start the comparison
+ *
+ * @return 1 if it does
+ */
+int cstring_sends_with(const char self[], const char find[]);
+
+/**
+ * Find the given substring in this one.
+ *
+ * @param self the cstring to work on
+ * @param find the string to find
+ * @param start_index the index at which to start the search
+ *
+ * @return the start index of the found string if found, or a negative value
+ * if not
+ */
+long cstring_find(const char self[], const char find[], size_t rstart_index);
+
+/**
+ * Find the given substring in this one, but search in the reverse direction.
+ *
+ * @param self the cstring to work on
+ * @param find the string to find
+ * @param rstart_index the index at which to start the search, or 0 for
+ *        "end of string" (remember that it is reverse, you would then never
+ *        find anything with a real rstart_index of 0), or a negative value
+ *        to count from the end of the string (-2 means 2 character before the
+ *        end)
+ *
+ * @return the start index of the found string if found, or a negative value
+ * if not
+ */
+long cstring_rfind(char self[], const char find[], size_t rstart_index);
+
+/**
+ * Check if the given string is contained by this one.
+ *
+ * @param self the string to work on
+ * @param find the string to find
+ * @param start_index the index at which to start the comparison
+ *
+ * @return the start index of the found string if found, or a negative value if not
+ */
+long long cstring_sfind(const char self[], const char find[],
+               size_t start_index);
+
+/**
+ * Check if any of the given characters (in a char* which MUST end with '\0') is found.
+ *
+ * @param self the cstring to work on
+ * @param find the characters to find, which MUST be an array of char that ends with '\0'
+ * @param start_index the index at which to start the comparison
+ *
+ * @return the start index of the first found character if found, or a negative value if not
+ */
+long long cstring_find_any(cstring *self, const char find[], size_t start_index);
+
+/**
+ * Clear (truncate its size to 0) the given cstring.
+ *
+ * @param self the cstring to work on
+ */
+void cstring_clear(cstring *self);
+
+/**
+ * Convert this cstring into a string
+ * This means that you MUST NOT call cstring_free nor use the cstring anymore.
+ * NULL will return NULL.
+ *
+ * @param self the cstring to work on
+ */
+char *cstring_convert(cstring *self);
+
+/**
+ * Clone this cstring.
+ * NULL will return NULL.
+ *
+ * @param self the cstring to clone
+ */
+cstring *cstring_clone(cstring *self);
+
+/**
+ * Clone this string into a new cstring.
+ * NULL will return NULL.
+ *
+ * @param self the string to clone
+ */
+cstring *cstring_clones(const char self[]);
+
+/**
+ * Clone this cstring into a new string.
+ * NULL will return NULL.
+ *
+ * @param self the cstring to clone
+ */
+char *cstring_sclone(cstring *self);
+
+/**
+ * Clone this string into a new string.
+ * NULL will return NULL.
+ *
+ * @param self the string to clone
+ */
+char *cstring_sclones(const char self[]);
+
+/**
+ * Encode the string to BASE64.
+ *
+ * @param self the cstring to encode
+ */
+cstring *cstring_to_b64(cstring *self);
+
+/**
+ * Encode the string to BASE64.
+ *
+ * @param self the cstring to encode
+ */
+char *cstring_to_sb64(cstring *self);
+
+/**
+ * Encode the string to BASE64.
+ *
+ * @param self the string to encode
+ * @param size the size of the data (e.g., strlen(self))
+ */
+char *cstring_to_sb64s(char *self, size_t size);
+
+/**
+ * Decode the string to BASE64.
+ *
+ * @param self the cstring to decode
+ */
+cstring *cstring_from_b64(cstring *self);
+
+/**
+ * Decode the string to BASE64.
+ *
+ * @param self the cstring to decode
+ */
+char *cstring_from_sb64(cstring *self);
+
+/**
+ * Decode the string to BASE64.
+ *
+ * @param self the string to decode
+ */
+char *cstring_from_sb64s(char *self);
+
+/**
+ * Trim this cstring of all trailing 'car' instances.
+ *
+ * @param self the cstring to work on
+ * @param car the character to trim (right only)
+ *
+ * @return a right trimmed cstring
+ */
+void cstring_rtrim(cstring *self, char car);
+
+
+/**
+ * Trim this cstring of all 'car' instances from the start and/or the
+ * end of the string.
+ * 
+ * @param self the cstring to work on
+ * @param car the character to trim
+ * 
+ * @return a trimmed cstring
+ */
+void cstring_trim(cstring *self, char car);
+
+/**
+ * Compact the memory used by this cstring.
+ *
+ * @param self the cstring to work on
+ */
+void cstring_compact(cstring *self);
+
+/**
+ * Change the case to upper-case (UTF-8 compatible, but the string MUST be
+ * whole).
+ *
+ * @param self the cstring to work on
+ */
+void cstring_toupper(cstring *self);
+
+/**
+ * Change the case to lower-case (UTF-8 compatible, but the string MUST be
+ * whole).
+ *
+ * @param self the cstring to work on
+ */
+void cstring_tolower(cstring *self);
+
+/**
+ * Read a whole line (CR, LN or CR+LN terminated) from the given file stream.
+ *
+ * @param self the cstring to read into
+ * @param file the file to read
+ *
+ * @return 1 if a line was read, 0 if not
+ */
+int cstring_readline(cstring *self, FILE *file);
+
+/**
+ * Read a whole line (CR, LN or CR+LN terminated) from the given socket.
+ *
+ * @param self the cstring to read into
+ * @param fd the socket to read from
+ *
+ * @return 1 if a line was read, 0 if not
+ */
+int cstring_readnet(cstring *self, int fd);
+
+/**
+ * Add a path to the given cstring (if it is currently empty, it
+ * will result in a root path).
+ *
+ * Will be separated by a forward '/' except on non-standard systems
+ * that uses reverse slash (i.e., Windows).
+ *
+ * @param self the base cstring (empty for a root path)
+ * @param subpath the path component to add
+ */
+void cstring_add_path(cstring *self, const char subpath[]);
+
+//TODO: desc
+int cstring_pop_path(cstring *self, int how_many);
+
+/**
+ * Return the basename component of this path (for instance,
+ * '/home/user/file.ext' becomes 'file.ext').
+ *
+ * @param path the path to get the dir of (it can be a dir itself)
+ *
+ * @return a new string representing the parent directory
+ */
+char *cstring_basename(const char path[]);
+
+/**
+ * Return the dirname of this path (for instance,
+ * '/home/user/file.ext' becomes '/home/user').
+ *
+ * @param path the path to get the dir of (it can be a dir itself)
+ *
+ * @return a new string representing the parent directory
+ */
+char *cstring_dirname(const char path[]);
+
+/**
+ * Return the latest path component of this path (usually a FILE).
+ *
+ * @param path the path to get the basename of (it can be a dir itself)
+ *
+ * @return a new cstring representing the latest path component
+ */
+cstring *cstring_getfile(cstring *path);
+
+/**
+ * Return the latest path component of this path (usually a FILE).
+ *
+ * @param path the path to get the basename of (it can be a dir itself)
+ *
+ * @return a new string representing the latest path component
+ */
+cstring *cstring_getfiles(const char path[]);
+
+/**
+ * Remove all the \r and \n at the end of the given cstring.
+ *
+ * @param self the cstring to change
+ *
+ * @return how many removed characters
+ */
+size_t cstring_remove_crlf(cstring *self);
+
+/**
+ * Remove all the \r and \n at the end of the given string.
+ * 
+ * @param self the string to change
+ * @param n the size of the string
+ *
+ * @return how many removed characters
+ */
+size_t cstring_sremove_crlf(char *self, size_t n);
+
+/**
+ * Check if the string is whole (i.e., it doesn't contain incomplete UTF-8
+ * sequences).
+ * 
+ * @return TRUE if it is whole
+ */
+int cstring_is_whole(cstring *self);
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
diff --git a/src/utils/net.c b/src/utils/net.c
new file mode 100644 (file)
index 0000000..f9fe50d
--- /dev/null
@@ -0,0 +1,264 @@
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2012 Niki Roo
+ *
+ * This program 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
+ * (at your option) any later version.
+ *
+ * This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <signal.h>
+
+#ifndef WIN32
+    #include <fcntl.h>
+#endif
+
+#include "net.h"
+#include "cstring.h"
+
+#define bool int
+#define true 1
+#define false 0
+
+/**
+ * Get the sockaddr, IPv4 or IPv6.
+ * @param sa the socket address is in this structure
+ * @return the sockaddr_in or sockaddr_in6 inside this socketaddr,
+ *                depending if it is IPv4 or IPv6
+ */
+void *get_in_addr(struct sockaddr *sa);
+
+#ifndef WIN32
+/**
+ * Reap all zombie processes.
+ * This function will be called when a child process terminates, and
+ * will loop on all zombie processes to properly acknowledge them
+ * so they can die.
+ *
+ * @param s
+ */
+void sigchld_handler(int pid);
+
+void sigchld_handler(int pid) {
+       if (pid > 0 && pid < 0) pid = 0;
+
+       // Reap all zombie processes
+       while (waitpid(-1, NULL, WNOHANG) > 0);
+}
+#endif
+
+int net_init(){
+#if defined (WIN32)
+       WSADATA WSAData;
+       return !WSAStartup(MAKEWORD(2,2), &WSAData);
+#endif
+       return 1;
+}
+
+void net_cleanup(){
+#if defined (WIN32)
+       WSACleanup();
+#endif
+}
+
+int net_set_non_blocking(int fd) {
+       return net_set_blocking(fd, 0);
+}
+
+int net_set_blocking(int fd, int block) {
+       int flags;
+
+/* If they have O_NONBLOCK, use the POSIX way to do it */
+#if defined (O_NONBLOCK)
+       /* O_NONBLOCK is defined but broken on SunOS 4.1.x and AIX 3.2.5. */
+       if ((flags = fcntl(fd, F_GETFL, 0)) == -1) {
+               flags = 0;
+       }
+       if (block) {
+               return fcntl(fd, F_SETFL, flags ^ O_NONBLOCK);
+       }
+       else {
+               return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+       }
+#else
+       flags = block?0:1;
+       return ioctl(fd, FIONBIO, (int)(&flags));
+#endif
+}
+
+int net_connect(const char server[], int port) {
+       int sockfd;
+       struct addrinfo hints, *servinfo, *p;
+       int rv;
+       cstring *str;
+
+       memset(&hints, 0, sizeof(struct addrinfo));
+       hints.ai_family = AF_UNSPEC;
+       hints.ai_socktype = SOCK_STREAM;
+
+       // convert the port number to a string
+       str = new_cstring();
+       cstring_addp(str, "%i", port);
+       rv = getaddrinfo(server, str->string, &hints, &servinfo);
+       free_cstring(str);
+       //
+
+       if (rv != 0) {
+               // DO NOT dirty the stderr
+               //fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
+               return -1;
+       }
+
+       // loop through all the results and connect to the first we can
+       for (p = servinfo; p != NULL; p = p->ai_next) {
+               if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol))
+                               == -1) {
+                       //perror("client: socket");
+                       continue;
+               }
+
+               if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
+                       close(sockfd);
+                       //perror("client: connect");
+                       continue;
+               }
+
+               break;
+       }
+
+       if (p == NULL) {
+               //fprintf(stderr, "client: failed to connect\n");
+               return -1;
+       }
+       
+       freeaddrinfo(servinfo);
+
+       return sockfd;
+}
+
+int net_accept(int ssocketd) {
+       struct sockaddr_storage their_addr;
+       socklen_t sin_size;
+
+       sin_size = sizeof(their_addr);
+       return accept(ssocketd, (struct sockaddr *) &their_addr, &sin_size);
+}
+
+int net_listen(int port, int backlog) {
+#ifndef WIN32
+    struct sigaction sa;
+#endif
+       int sockfd;
+       struct addrinfo hints, *servinfo, *p;
+       char yes = 1;
+       int rv;
+       cstring *str;
+
+       memset(&hints, 0, sizeof hints);
+       hints.ai_family = AF_UNSPEC;
+       hints.ai_socktype = SOCK_STREAM;
+       hints.ai_flags = AI_PASSIVE; // use my IP
+
+       // convert the port number to a string
+       str = new_cstring();
+       cstring_addp(str, "%d", port);
+       rv = getaddrinfo(NULL, str->string, &hints, &servinfo);
+       free_cstring(str);
+       //
+
+       if (rv != 0) {
+               // DO NOT dirty the stderr
+               //fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
+               return -1;
+       }
+
+       // loop through all the results and bind to the first we can
+       for (p = servinfo; p != NULL; p = p->ai_next) {
+               if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
+                       //perror("server: socket");
+                       continue;
+               }
+
+               if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
+                       //perror("setsockopt");
+                       return -1;
+               }
+
+               if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
+                       close(sockfd);
+                       //perror("server: bind");
+                       continue;
+               }
+
+               break;
+       }
+
+       if (p == NULL) {
+               //fprintf(stderr, "server: failed to bind\n");
+               return -1;
+       }
+
+       // all done with this structure
+       freeaddrinfo(servinfo);
+       
+       if (listen(sockfd, backlog) == -1) {
+               //perror("listen");
+               return -1;
+       }
+
+#ifndef WIN32
+       sa.sa_handler = sigchld_handler;
+       sigemptyset(&sa.sa_mask);
+       sa.sa_flags = SA_RESTART;
+       if (sigaction(SIGCHLD, &sa, NULL) == -1) {
+               //perror("sigaction");
+               return -1;
+       }
+#endif
+
+       return sockfd;
+}
+
+void net_close_socketd(int socketd) {
+       close(socketd);
+}
+
+#ifndef MSG_NOSIGNAL
+#define MSG_NOSIGNAL 0
+#endif
+ssize_t net_write(int fd, const void *buf, size_t n) {
+       // In UNIX: send() with flag set to '0' == write()
+       // In WIN32: cannot write() to a socket
+       return send(fd, (char *)buf, n, MSG_NOSIGNAL);
+}
+
+ssize_t net_read(int fd, void *buf, size_t nbytes) {
+       // In UNIX: recv() with flag set to '0' == send()
+       // In WIN32: cannot read() from a socket
+       return recv(fd, (char *)buf, nbytes, 0);
+}
+
+void *get_in_addr(struct sockaddr *sa) {
+       if (sa->sa_family == AF_INET) {
+               return &(((struct sockaddr_in*) sa)->sin_addr);
+       }
+       else {
+               return &(((struct sockaddr_in6*) sa)->sin6_addr);
+       }
+}
+
diff --git a/src/utils/net.h b/src/utils/net.h
new file mode 100644 (file)
index 0000000..9652a4d
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2012 Niki Roo
+ *
+ * This program 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
+ * (at your option) any later version.
+ *
+ * This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file
+ * @author niki
+ * @date November 2011
+ *
+ * Allows you to make connections to/from a server, and to send/receive data.
+ */
+
+#ifndef NET_H
+#define NET_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// WHY ??
+#ifdef _WIN32
+       #ifndef WIN32
+               #define WIN32
+       #endif
+#endif
+
+#ifdef WIN32
+    #ifndef _WIN32_WINNT
+        #define _WIN32_WINNT 0x501
+    #endif
+       #include <winsock2.h>
+       #include <ws2tcpip.h>
+       typedef SSIZE_T ssize_t;
+       #define close(s) closesocket(s)
+       #define ioctl(a,b,c) ioctlsocket(a,b,c)
+       #pragma comment(lib, "wsock32.lib");
+       #pragma comment(lib, "ws2_32.lib");
+#else
+       #include <unistd.h>
+       #include <sys/socket.h>
+       #include <netinet/in.h>
+       #include <netdb.h>
+       #include <arpa/inet.h>
+       #include <sys/wait.h>
+#endif
+
+/* for ssize_t */
+#include <sys/types.h>
+
+/**
+ * Obsolete, see net_set_blocking(..)
+ */
+int net_set_non_blocking(int fd);
+
+/**
+ * You must call this function before doing any network related operation,
+ * because of some legacy WIN32 rule.
+ * (It is a noop on all other platforms).
+ *
+ * @return 0 when the Windows host does not support WinSock and thus,
+ *             no network for you (you still need to call net_cleanup)
+ * 
+ */
+int net_init();
+
+/**
+ * You must call this function after you are done using network related
+ * operations within this DLL, because of some legacy WIN32 rule.
+ * (It is a noop on all other platforms).
+ */
+void net_cleanup();
+
+/**
+ * Set the given socket to (non-)blocking I/O mode.
+ * This function can work with file sockets, too.
+ *
+ * @param fd the file descriptor or socket to change
+ * @param block 1 to block, 0 not to block
+ * 
+ * @return 1 if success
+ */
+int net_set_blocking(int fd, int block);
+
+/**
+ * Connect to this server on this port, and return a socket descriptor
+ * to write to or read from it.
+ *
+ * @param server the server to connect to
+ * @param port the port to connect on
+ *
+ * @return the server socket or -1 if error
+ */
+int net_connect(const char server[], int port);
+
+/**
+ * Open a port and returns a (server) socket descriptor from which you can
+ *               accept connection.
+ *
+ * @param port the port to connect on
+ * @param backlog the maximum number of client connections we will queue for
+ *     this socket until you handle them
+ *
+ * @return the server socket, or a negative value on error
+ */
+int net_listen(int port, int backlog);
+
+/**
+ * Block (or not) and wait for a client to connect on this socket. 
+ * When this is done, return a socket to this specific client/server
+ * connection. It can takes the connections from a queue,
+ * as defined in net_listen.
+ *
+ * @param ssocketd the server socket on which to accept a connection
+ *
+ * @return the socket, or a negative value on error
+ */
+int net_accept(int ssocketd);
+
+/**
+ * Close a socket (or a server socket).
+ *
+ * @param socketd the (server) socket to close
+ */
+void net_close_socketd(int socketd);
+
+/**
+ * Write to this socket, as you would with a file.
+ *
+ * @param fd the socket to write to
+ * @param buf the buffer to read from
+ * @param n the number of bytes to write
+ *
+ * @return the actual number of bytes written or a
+ *                negative number if error
+ */
+ssize_t net_write(int fd, const void *buf, size_t n);
+
+/**
+ * Read from this socket, as you would with a file.
+ *
+ * @param fd the socket to read from
+ * @param buf the buffer to write to
+ * @param nbytes the number of bytes to read
+ *
+ * @return the actual number of bytes read
+ */
+ssize_t net_read(int fd, void *buf, size_t nbytes);
+
+#endif
+
+#ifdef __cplusplus
+extern }
+#endif