diff --git a/.travis.yml b/.travis.yml index a3710f0c..b043dba1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,50 +4,55 @@ compiler: - clang before_script: -# installing libsodium, needed for Core - - git clone git://github.com/jedisct1/libsodium.git + #installing libsodium, needed for Core + - git clone git://github.com/jedisct1/libsodium.git > /dev/null - cd libsodium - - git checkout tags/0.4.2 - - ./autogen.sh - - ./configure && make check -j3 - - sudo make install + - git checkout tags/0.4.2 > /dev/null + - ./autogen.sh > /dev/null + - ./configure > /dev/null + - make check -j3 > /dev/null + - sudo make install >/dev/null - cd .. -# installing libconfig, needed for DHT_bootstrap_daemon - - wget http://www.hyperrealm.com/libconfig/libconfig-1.4.9.tar.gz - - tar -xvzf libconfig-1.4.9.tar.gz + #installing libconfig, needed for DHT_bootstrap_daemon + - wget http://www.hyperrealm.com/libconfig/libconfig-1.4.9.tar.gz > /dev/null + - tar -xvzf libconfig-1.4.9.tar.gz > /dev/null - cd libconfig-1.4.9 - - ./configure && make -j3 - - sudo make install + - ./configure > /dev/null + - make -j3 > /dev/null + - sudo make install > /dev/null - cd .. -# installing libopus, needed for audio encoding/decoding - - wget http://downloads.xiph.org/releases/opus/opus-1.0.3.tar.gz - - tar xzvf opus-1.0.3.tar.gz + #installing libopus, needed for audio encoding/decoding + - wget http://downloads.xiph.org/releases/opus/opus-1.0.3.tar.gz > /dev/null + - tar xzvf opus-1.0.3.tar.gz > /dev/null - cd opus-1.0.3 - - ./configure && make -j3 - - sudo make install + - ./configure > /dev/null + - make -j3 > /dev/null + - sudo make install > /dev/null - cd .. -# installing libsdl1.2, needed for displaying video frames - - wget http://www.libsdl.org/release/SDL-1.2.15.tar.gz - - tar -xvzf SDL-1.2.15.tar.gz + #installing libsdl1.2, needed for displaying video frames + - wget http://www.libsdl.org/release/SDL-1.2.15.tar.gz > /dev/null + - tar -xvzf SDL-1.2.15.tar.gz > /dev/null - cd SDL-1.2.15 - - ./configure && make -j3 - - sudo make install + - ./configure > /dev/null + - make -j3 /dev/null + - sudo make install > /dev/null - cd .. -# installing libopenal, needed for audio capture/playback - - sudo apt-get install libopenal-dev -# installing yasm, needed for compiling ffmpeg - - sudo apt-get install yasm -# installing ffmpeg, needed for capturing and encoding/decoding video - - wget https://www.ffmpeg.org/releases/ffmpeg-2.0.2.tar.gz - - tar -xvzf ffmpeg-2.0.2.tar.gz + #installing libopenal, needed for audio capture/playback + - sudo apt-get install libopenal-dev > /dev/null + #installing yasm, needed for compiling ffmpeg + - sudo apt-get install yasm > /dev/null + #installing ffmpeg, needed for capturing and encoding/decoding video + - wget https://www.ffmpeg.org/releases/ffmpeg-2.0.2.tar.gz > /dev/null + - tar -xvzf ffmpeg-2.0.2.tar.gz > /dev/null - cd ffmpeg-2.0.2 - - ./configure && make -j3 - - sudo make install + - ./configure > /dev/null + - make -j3 >/dev/null + - sudo make install > /dev/null - cd .. -# creating librarys' links and updating cache - - sudo ldconfig -# installing check, needed for unit tests - - sudo apt-get install check + #creating libraries links and updating cache + - sudo ldconfig > /dev/null + #installing check, needed for unit tests + - sudo apt-get install check > /dev/null script: - autoreconf -i diff --git a/INSTALL.md b/INSTALL.md index 9eea323a..81db3a45 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -56,7 +56,7 @@ cd .. ``` If your default prefix is /usr/local and you happen to get an error that says "error while loading shared libraries: libtoxcore.so.0: cannot open shared object file: No such file or directory", then you can try running ```sudo ldconfig```. If that doesn't fix it, run: ``` -sudo echo "/usr/local/lib/" >> /etc/ld.so.conf.d/locallib.conf +echo '/usr/local/lib/' | sudo tee -a /etc/ld.so.conf.d/locallib.conf sudo ldconfig ``` diff --git a/auto_tests/Makefile.inc b/auto_tests/Makefile.inc index 9c06bc78..1831fc50 100644 --- a/auto_tests/Makefile.inc +++ b/auto_tests/Makefile.inc @@ -1,8 +1,8 @@ if BUILD_TESTS -TESTS = messenger_autotest crypto_test network_test +TESTS = messenger_autotest crypto_test network_test assoc_test -check_PROGRAMS = messenger_autotest crypto_test network_test +check_PROGRAMS = messenger_autotest crypto_test network_test assoc_test AUTOTEST_CFLAGS = \ $(LIBSODIUM_CFLAGS) \ @@ -38,6 +38,13 @@ network_test_CFLAGS = $(AUTOTEST_CFLAGS) network_test_LDADD = $(AUTOTEST_LDADD) +assoc_test_SOURCES = ../auto_tests/assoc_test.c + +assoc_test_CFLAGS = $(AUTOTEST_CFLAGS) + +assoc_test_LDADD = $(AUTOTEST_LDADD) + + endif EXTRA_DIST += $(top_srcdir)/auto_tests/friends_test.c diff --git a/auto_tests/assoc_test.c b/auto_tests/assoc_test.c new file mode 100644 index 00000000..af88afa7 --- /dev/null +++ b/auto_tests/assoc_test.c @@ -0,0 +1,158 @@ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define AUTO_TEST +#include "../toxcore/DHT.h" +#include "../toxcore/assoc.h" +#include "../toxcore/util.h" + +#include +#include +#include + +#include + +START_TEST(test_basics) +{ + /* TODO: real test */ + uint8_t id[CLIENT_ID_SIZE]; + Assoc *assoc = new_Assoc_default(id); + ck_assert_msg(assoc != NULL, "failed to create default assoc"); + + kill_Assoc(assoc); + assoc = new_Assoc(17, 4, id); /* results in an assoc of 16/3 */ + ck_assert_msg(assoc != NULL, "failed to create customized assoc"); + + IP_Port ipp; + ipp.ip.family = AF_INET; + ipp.ip.ip4.uint8[0] = 1; + ipp.port = htons(12345); + + IPPTs ippts_send; + ippts_send.ip_port = ipp; + ippts_send.timestamp = unix_time(); + IP_Port ipp_recv = ipp; + + uint8_t res = Assoc_add_entry(assoc, id, &ippts_send, &ipp_recv, 0); + ck_assert_msg(res == 0, "stored self as entry: expected %u, got %u", 0, res); + + id[0]++; + + res = Assoc_add_entry(assoc, id, &ippts_send, &ipp_recv, 0); + ck_assert_msg(res == 1, "failed to store entry: expected %u, got %u", 1, res); + + Assoc_close_entries close_entries; + memset(&close_entries, 0, sizeof(close_entries)); + close_entries.count = 4; + close_entries.count_good = 2; + close_entries.wanted_id = id; + + Client_data *entries[close_entries.count]; + close_entries.result = entries; + + uint8_t found = Assoc_get_close_entries(assoc, &close_entries); + ck_assert_msg(found == 1, "get_close_entries(): expected %u, got %u", 1, found); +} +END_TEST + +START_TEST(test_fillup) +{ + /* TODO: real test */ + int i, j; + uint8_t id[CLIENT_ID_SIZE]; + //uint32_t a = current_time(); + uint32_t a = 2710106197; + srand(a); + for(i = 0; i < CLIENT_ID_SIZE; ++i) { + id[i] = rand(); + } + Assoc *assoc = new_Assoc(6, 15, id); + ck_assert_msg(assoc != NULL, "failed to create default assoc"); + struct entry { + uint8_t id[CLIENT_ID_SIZE]; + IPPTs ippts_send; + IP_Port ipp_recv; + }; + unsigned int fail = 0; + struct entry entries[128]; + struct entry closest[8]; + for(j = 0; j < 128; ++j) { + + for(i = 0; i < CLIENT_ID_SIZE; ++i) { + entries[j].id[i] = rand(); + } + IP_Port ipp; + ipp.ip.family = AF_INET; + ipp.ip.ip4.uint32 = rand(); + ipp.port = rand(); + entries[j].ippts_send.ip_port = ipp; + entries[j].ippts_send.timestamp = unix_time(); + ipp.ip.ip4.uint32 = rand(); + ipp.port = rand(); + entries[j].ipp_recv = ipp; + if (j % 16 == 0) { + memcpy(entries[j].id, id, CLIENT_ID_SIZE - 30); + memcpy(&closest[j/16], &entries[j], sizeof(struct entry)); + + } + uint8_t res = Assoc_add_entry(assoc, entries[j].id, &entries[j].ippts_send, &entries[j].ipp_recv, 1); + ck_assert_msg(res == 1, "failed to store entry: expected %u, got %u, j = %u", 1, res, j); + } + int good = 0; + Assoc_close_entries close_entries; + memset(&close_entries, 0, sizeof(close_entries)); + close_entries.count = 8; + close_entries.count_good = 8; + close_entries.wanted_id = id; + + Client_data *entri[close_entries.count]; + close_entries.result = entri; + + uint8_t found = Assoc_get_close_entries(assoc, &close_entries); + ck_assert_msg(found == 8, "get_close_entries(): expected %u, got %u", 1, found); + for (i = 0; i < 8; ++i) { + for (j = 0; j < 8; ++j) { + if (id_equal(entri[j]->client_id, closest[i].id)) + ++good; + } + }ck_assert_msg(good == 8, "Entries found were not the closest ones. Only %u/8 were.", good); + //printf("good: %u %u %u\n", good, a, ((uint32_t)current_time() - a)); +} +END_TEST + +#define DEFTESTCASE(NAME) \ + TCase *tc_##NAME = tcase_create(#NAME); \ + tcase_add_test(tc_##NAME, test_##NAME); \ + suite_add_tcase(s, tc_##NAME); + +#define DEFTESTCASE_SLOW(NAME, TIMEOUT) \ + DEFTESTCASE(NAME) \ + tcase_set_timeout(tc_##NAME, TIMEOUT); + +Suite *Assoc_suite(void) +{ + Suite *s = suite_create("Assoc"); + + DEFTESTCASE(basics); + DEFTESTCASE(fillup); + return s; +} + +int main(int argc, char *argv[]) +{ + unix_time_update(); + Suite *Assoc = Assoc_suite(); + SRunner *test_runner = srunner_create(Assoc); + + srunner_set_fork_status(test_runner, CK_NOFORK); + + srunner_run_all(test_runner, CK_NORMAL); + + int number_failed = srunner_ntests_failed(test_runner); + + srunner_free(test_runner); + + return number_failed; +} diff --git a/testing/nTox.c b/testing/nTox.c index 9b22ff5b..02b93d6f 100644 --- a/testing/nTox.c +++ b/testing/nTox.c @@ -42,6 +42,7 @@ #include #include +#include #ifdef __WIN32__ #define c_sleep(x) Sleep(1*x) @@ -51,14 +52,57 @@ #endif char lines[HISTORY][STRING_LENGTH]; +uint8_t flag[HISTORY]; char input_line[STRING_LENGTH]; -char *help = "[i] commands:\n/f ID (to add friend)\n/m friendnumber message " - "(to send message)\n/s status (to change status)\n[i] /l list (l" - "ist friends)\n/h for help\n/i for info\n/n nick (to change nick" - "name)\n/q (to quit)"; +/* wrap: continuation mark */ +const size_t wrap_cont_len = 3; +const char wrap_cont_str[] = "\n+ "; + +#define STRING_LENGTH_WRAPPED (STRING_LENGTH + 16 * (wrap_cont_len + 1)) + +/* documented: fdmnlsahxgiztq(c[rfg]) */ +/* undocumented: d (tox_do()) */ + +/* 251+1 characters */ +char *help_main = + "[i] Available main commands:\n+ " + "/x (to print one's own id)|" + "/s status (to change status, e.g. AFK)|" + "/n nick (to change your nickname)|" + "/q (to quit)|" + "/cr (to reset conversation)|" + "/h friend (for friend related commands)|" + "/h group (for group related commands)"; + +/* 190+1 characters */ +char *help_friend1 = + "[i] Available friend commands (1/2):\n+ " + "/l list (to list friends)|" + "/r friend no. (to remove from the friend list)|" + "/f ID (to send a friend request)|" + "/a request no. (to accept a friend request)"; + +/* 187+1 characters */ +char *help_friend2 = + "[i] Available friend commands (2/2):\n+ " + "/m friend no. message (to send a message)|" + "/t friend no. filename (to send a file to a friend)|" + "/cf friend no. (to talk to that friend per default)"; + +/* 253+1 characters */ +char *help_group = + "[i] Available group commands:\n+ " + "/g (to create a group)|" + "/i friend no. group no. (to invite a friend to a group)|" + "/z group no. message (to send a message to a group)|" + "/p group no. (to list a group's peers)|" + "/cg group no. (to talk to that group per default)"; + int x, y; +int conversation_default = 0; + typedef struct { uint8_t id[TOX_CLIENT_ID_SIZE]; uint8_t accepted; @@ -87,17 +131,17 @@ void send_filesenders(Tox *m) continue; while (1) { - if (!tox_file_senddata(m, file_senders[i].friendnum, file_senders[i].filenumber, file_senders[i].nextpiece, - file_senders[i].piecelength)) + if (tox_file_send_data(m, file_senders[i].friendnum, file_senders[i].filenumber, file_senders[i].nextpiece, + file_senders[i].piecelength) == -1) break; - file_senders[i].piecelength = fread(file_senders[i].nextpiece, 1, tox_filedata_size(m, file_senders[i].friendnum), + file_senders[i].piecelength = fread(file_senders[i].nextpiece, 1, tox_file_data_size(m, file_senders[i].friendnum), file_senders[i].file); if (file_senders[i].piecelength == 0) { fclose(file_senders[i].file); file_senders[i].file = 0; - tox_file_sendcontrol(m, file_senders[i].friendnum, 0, file_senders[i].filenumber, 3, 0, 0); + tox_file_send_control(m, file_senders[i].friendnum, 0, file_senders[i].filenumber, 3, 0, 0); char msg[512]; sprintf(msg, "[t] %u file transfer: %u completed", file_senders[i].friendnum, file_senders[i].filenumber); new_lines(msg); @@ -116,13 +160,13 @@ int add_filesender(Tox *m, uint16_t friendnum, char *filename) fseek(tempfile, 0, SEEK_END); uint64_t filesize = ftell(tempfile); fseek(tempfile, 0, SEEK_SET); - int filenum = tox_new_filesender(m, friendnum, filesize, (uint8_t *)filename, strlen(filename) + 1); + int filenum = tox_new_file_sender(m, friendnum, filesize, (uint8_t *)filename, strlen(filename) + 1); if (filenum == -1) return -1; file_senders[numfilesenders].file = tempfile; - file_senders[numfilesenders].piecelength = fread(file_senders[numfilesenders].nextpiece, 1, tox_filedata_size(m, + file_senders[numfilesenders].piecelength = fread(file_senders[numfilesenders].nextpiece, 1, tox_file_data_size(m, file_senders[numfilesenders].friendnum), file_senders[numfilesenders].file); file_senders[numfilesenders].friendnum = friendnum; @@ -193,46 +237,101 @@ uint32_t resolve_addr(const char *address) return addr; } +#define FRADDR_TOSTR_CHUNK_LEN 8 +#define FRADDR_TOSTR_BUFSIZE (TOX_FRIEND_ADDRESS_SIZE * 2 + TOX_FRIEND_ADDRESS_SIZE / FRADDR_TOSTR_CHUNK_LEN + 1) + +static void fraddr_to_str(uint8_t *id_bin, char *id_str) +{ + uint i, delta = 0, pos_extra, sum_extra = 0; + + for (i = 0; i < TOX_FRIEND_ADDRESS_SIZE; i++) { + sprintf(&id_str[2 * i + delta], "%02hhX", id_bin[i]); + + if ((i + 1) == TOX_CLIENT_ID_SIZE) + pos_extra = 2 * (i + 1) + delta; + + if (i >= TOX_CLIENT_ID_SIZE) + sum_extra |= id_bin[i]; + + if (!((i + 1) % FRADDR_TOSTR_CHUNK_LEN)) { + id_str[2 * (i + 1) + delta] = ' '; + delta++; + } + } + + id_str[2 * i + delta] = 0; + + if (!sum_extra) + id_str[pos_extra] = 0; +} + void get_id(Tox *m, char *data) { sprintf(data, "[i] ID: "); int offset = strlen(data); uint8_t address[TOX_FRIEND_ADDRESS_SIZE]; - tox_getaddress(m, address); + tox_get_address(m, address); + fraddr_to_str(address, data + offset); +} - uint32_t i = 0; +int getfriendname_terminated(Tox *m, int friendnum, char *namebuf) +{ + int res = tox_get_name(m, friendnum, (uint8_t *)namebuf); - for (; i < TOX_FRIEND_ADDRESS_SIZE; i++) { - sprintf(data + 2 * i + offset, "%02X ", address[i]); + if (res >= 0) + namebuf[res] = 0; + else + namebuf[0] = 0; + + return res; +} + +void new_lines_mark(char *line, uint8_t special) +{ + int i = 0; + + for (i = HISTORY - 1; i > 0; i--) { + strncpy(lines[i], lines[i - 1], STRING_LENGTH - 1); + flag[i] = flag[i - 1]; } + + strncpy(lines[0], line, STRING_LENGTH - 1); + flag[i] = special; + + do_refresh(); } void new_lines(char *line) { - int i = 0; - - for (i = HISTORY - 1; i > 0; i--) - strncpy(lines[i], lines[i - 1], STRING_LENGTH - 1); - - strncpy(lines[0], line, STRING_LENGTH - 1); - do_refresh(); + new_lines_mark(line, 0); } +const char ptrn_friend[] = "[i] Friend %i: %s\n+ id: %s"; +const int id_str_len = TOX_FRIEND_ADDRESS_SIZE * 2 + 3; void print_friendlist(Tox *m) { - char name[TOX_MAX_NAME_LENGTH]; - int i = 0; new_lines("[i] Friend List:"); - while (tox_getname(m, i, (uint8_t *)name) != -1) { - /* account for the longest name and the longest "base" string */ - char fstring[TOX_MAX_NAME_LENGTH + strlen("[i] Friend: NULL\n\tid: ")]; + char name[TOX_MAX_NAME_LENGTH + 1]; + uint8_t fraddr_bin[TOX_FRIEND_ADDRESS_SIZE]; + char fraddr_str[FRADDR_TOSTR_BUFSIZE]; + + /* account for the longest name and the longest "base" string and number (int) and id_str */ + char fstring[TOX_MAX_NAME_LENGTH + strlen(ptrn_friend) + 21 + id_str_len]; + + uint i = 0; + + while (getfriendname_terminated(m, i, name) != -1) { + if (!tox_get_client_id(m, i, fraddr_bin)) + fraddr_to_str(fraddr_bin, fraddr_str); + else + sprintf(fraddr_str, "???"); if (strlen(name) <= 0) { - sprintf(fstring, "[i] Friend: No Friend!\n\tid: %i", i); + sprintf(fstring, ptrn_friend, i, "No name?", fraddr_str); } else { - sprintf(fstring, "[i] Friend: %s\n\tid: %i", (uint8_t *)name, i); + sprintf(fstring, ptrn_friend, i, (uint8_t *)name, fraddr_str); } i++; @@ -240,41 +339,51 @@ void print_friendlist(Tox *m) } if (i == 0) - new_lines("\tno friends! D:"); + new_lines("+ no friends! D:"); } -char *format_message(Tox *m, char *message, int friendnum) +static int fmtmsg_tm_mday = -1; + +static void print_formatted_message(Tox *m, char *message, int friendnum, uint8_t outgoing) { - char name[TOX_MAX_NAME_LENGTH]; + char name[TOX_MAX_NAME_LENGTH + 1]; + getfriendname_terminated(m, friendnum, name); - if (friendnum != -1) { - tox_getname(m, friendnum, (uint8_t *)name); - } else { - tox_getselfname(m, (uint8_t *)name, sizeof(name)); - } - - char *msg = malloc(100 + strlen(message) + strlen(name) + 1); + char msg[100 + strlen(message) + strlen(name) + 1]; time_t rawtime; struct tm *timeinfo; time ( &rawtime ); timeinfo = localtime ( &rawtime ); - char *time = asctime(timeinfo); - size_t len = strlen(time); - time[len - 1] = '\0'; - if (friendnum != -1) { - sprintf(msg, "[%d] %s <%s> %s", friendnum, time, name, message); - } else { - // This message came from ourselves - sprintf(msg, "%s <%s> %s", time, name, message); + /* assume that printing the date once a day is enough */ + if (fmtmsg_tm_mday != timeinfo->tm_mday) { + fmtmsg_tm_mday = timeinfo->tm_mday; + /* strftime(msg, 100, "Today is %a %b %d %Y.", timeinfo); */ + /* %x is the locale's preferred date format */ + strftime(msg, 100, "Today is %x.", timeinfo); + new_lines(msg); } - return msg; + char time[64]; + /* strftime(time, 64, "%I:%M:%S %p", timeinfo); */ + /* %X is the locale's preferred time format */ + strftime(time, 64, "%X", timeinfo); + + if (outgoing) { + /* tgt: friend */ + sprintf(msg, "[%d] %s =>{%s} %s", friendnum, time, name, message); + } else { + /* src: friend */ + sprintf(msg, "[%d] %s <%s>: %s", friendnum, time, name, message); + } + + new_lines(msg); } -/* forward declaration */ +/* forward declarations */ static int save_data(Tox *m); +void print_groupchatpeers(Tox *m, int groupnumber); void line_eval(Tox *m, char *line) { @@ -286,14 +395,18 @@ void line_eval(Tox *m, char *line) new_lines(prompt); if (inpt_command == 'f') { // add friend command: /f ID - int i; + int i, delta = 0; char temp_id[128]; - for (i = 0; i < 128; i++) - temp_id[i] = line[i + prompt_offset]; + for (i = 0; i < 128; i++) { + temp_id[i - delta] = line[i + prompt_offset]; + + if ((temp_id[i - delta] == ' ') || (temp_id[i - delta] == '+')) + delta++; + } unsigned char *bin_string = hex_string_to_bin(temp_id); - int num = tox_addfriend(m, bin_string, (uint8_t *)"Install Gentoo", sizeof("Install Gentoo")); + int num = tox_add_friend(m, bin_string, (uint8_t *)"Install Gentoo", sizeof("Install Gentoo")); free(bin_string); char numstring[100]; @@ -329,7 +442,6 @@ void line_eval(Tox *m, char *line) } new_lines(numstring); - do_refresh(); } else if (inpt_command == 'd') { tox_do(m); } else if (inpt_command == 'm') { //message command: /m friendnumber messsage @@ -337,12 +449,12 @@ void line_eval(Tox *m, char *line) int num = strtoul(line + prompt_offset, posi, 0); if (**posi != 0) { - if (tox_sendmessage(m, num, (uint8_t *) *posi + 1, strlen(*posi + 1) + 1) < 1) { + if (tox_send_message(m, num, (uint8_t *) *posi + 1, strlen(*posi + 1) + 1) < 1) { char sss[256]; sprintf(sss, "[i] could not send message to friend num %u", num); new_lines(sss); } else { - new_lines(format_message(m, *posi + 1, -1)); + print_formatted_message(m, *posi + 1, num, 1); } } else new_lines("Error, bad input."); @@ -357,7 +469,7 @@ void line_eval(Tox *m, char *line) } name[i - 3] = 0; - tox_setname(m, name, i - 2); + tox_set_name(m, name, i - 2); char numstring[100]; sprintf(numstring, "[i] changed nick to %s", (char *)name); new_lines(numstring); @@ -374,11 +486,11 @@ void line_eval(Tox *m, char *line) } status[i - 3] = 0; - tox_set_statusmessage(m, status, strlen((char *)status) + 1); + tox_set_status_message(m, status, strlen((char *)status) + 1); char numstring[100]; sprintf(numstring, "[i] changed status to %s", (char *)status); new_lines(numstring); - } else if (inpt_command == 'a') { + } else if (inpt_command == 'a') { // /a #: accept uint8_t numf = atoi(line + 3); char numchar[100]; @@ -386,13 +498,11 @@ void line_eval(Tox *m, char *line) sprintf(numchar, "[i] you either didn't receive that request or you already accepted it"); new_lines(numchar); } else { - int num = tox_addfriend_norequest(m, pending_requests[numf].id); + int num = tox_add_friend_norequest(m, pending_requests[numf].id); if (num != -1) { pending_requests[numf].accepted = 1; - sprintf(numchar, "[i] friend request %u accepted", numf); - new_lines(numchar); - sprintf(numchar, "[i] added friendnumber %d", num); + sprintf(numchar, "[i] friend request %u accepted as friend no. %d", numf, num); new_lines(numchar); save_data(m); } else { @@ -400,10 +510,52 @@ void line_eval(Tox *m, char *line) new_lines(numchar); } } + } else if (inpt_command == 'r') { // /r #: remove friend + uint8_t numf = atoi(line + 3); - do_refresh(); + if (!tox_friend_exists(m, numf)) { + char err[64]; + sprintf(err, "You don't have a friend %i.", numf); + new_lines(err); + return; + } + + char msg[128 + TOX_MAX_NAME_LENGTH]; + char fname[TOX_MAX_NAME_LENGTH ]; + getfriendname_terminated(m, numf, fname); + sprintf(msg, "Are you sure you want to delete friend %i: %s? (y/n)", numf, fname); + input_line[0] = 0; + new_lines(msg); + + int c; + + do { + c = getchar(); + } while ((c != 'y') && (c != 'n') && (c != EOF)); + + if (c == 'y') { + int res = tox_del_friend(m, numf); + + if (res == 0) + sprintf(msg, "[i] [%i: %s] is no longer your friend", numf, fname); + else + sprintf(msg, "[i] failed to remove friend"); + + new_lines(msg); + } } else if (inpt_command == 'h') { //help - new_lines(help); + if (line[2] == ' ') { + if (line[3] == 'f') { + new_lines_mark(help_friend1, 1); + new_lines_mark(help_friend2, 1); + return; + } else if (line[3] == 'g') { + new_lines_mark(help_group, 1); + return; + } + } + + new_lines_mark(help_main, 1); } else if (inpt_command == 'x') { //info char idstring[200]; get_id(m, idstring); @@ -425,10 +577,17 @@ void line_eval(Tox *m, char *line) int groupnumber = strtoul(line + prompt_offset, posi, 0); if (**posi != 0) { - char msg[256 + 1024]; - sprintf(msg, "[g] sent message: %s to group num: %u returned: %u (0 means success)", *posi + 1, groupnumber, - tox_group_message_send(m, groupnumber, (uint8_t *)*posi + 1, strlen(*posi + 1) + 1)); - new_lines(msg); + int res = tox_group_message_send(m, groupnumber, (uint8_t *)*posi + 1, strlen(*posi + 1) + 1); + + if (res == 0) { + char msg[32 + STRING_LENGTH]; + sprintf(msg, "[g] #%u: YOU: %s", groupnumber, *posi + 1); + new_lines(msg); + } else { + char msg[128]; + sprintf(msg, "[i] could not send message to group no. %u: %i", groupnumber, res); + new_lines(msg); + } } } else if (inpt_command == 't') { char msg[512]; @@ -444,29 +603,267 @@ void line_eval(Tox *m, char *line) save_data(m); endwin(); exit(EXIT_SUCCESS); + } else if (inpt_command == 'c') { //set conversation partner + if (line[2] == 'r') { + if (conversation_default != 0) { + conversation_default = 0; + new_lines("[i] default conversation reset"); + } else + new_lines("[i] default conversation wasn't set, nothing to do"); + } else if (line[3] != ' ') { + new_lines("[i] invalid command"); + } else { + int num = atoi(line + 4); + + /* zero is also returned for not-a-number */ + if (!num && strcmp(line + 4, "0")) + num = -1; + + if (num < 0) + new_lines("[i] invalid command parameter"); + else if (line[2] == 'f') { + conversation_default = num + 1; + char buffer[128]; + sprintf(buffer, "[i] default conversation is now to friend %i", num); + new_lines(buffer); + } else if (line[2] == 'g') { + char buffer[128]; + conversation_default = - (num + 1); + sprintf(buffer, "[i] default conversation is now to group %i", num); + new_lines(buffer); + } else + new_lines("[i] invalid command"); + } + } else if (inpt_command == 'p') { //list peers + char *posi = NULL; + int group_number = strtoul(line + prompt_offset, &posi, 0); + + if (posi != NULL) { + char msg[64]; + int peer_cnt = tox_group_number_peers(m, group_number); + + if (peer_cnt < 0) { + new_lines("[g] Invalid group number."); + } else if (peer_cnt == 0) { + sprintf(msg, "[g] #%i: No peers in group.", group_number); + new_lines(msg); + } else { + sprintf(msg, "[g] #%i: Group has %i peers. Names:", group_number, peer_cnt); + new_lines(msg); + print_groupchatpeers(m, group_number); + } + } } else { new_lines("[i] invalid command"); } } else { - new_lines("[i] invalid command"); - //new_lines(line); + if (conversation_default != 0) { + if (conversation_default > 0) { + int friendnumber = conversation_default - 1; + uint32_t res = tox_send_message(m, friendnumber, (uint8_t *)line, strlen(line) + 1); + + if (res == 0) { + char sss[128]; + sprintf(sss, "[i] could not send message to friend no. %u", friendnumber); + new_lines(sss); + } else + print_formatted_message(m, line, friendnumber, 1); + } else { + int groupnumber = - conversation_default - 1; + int res = tox_group_message_send(m, groupnumber, (uint8_t *)line, strlen(line) + 1); + + if (res == 0) { + char msg[32 + STRING_LENGTH]; + sprintf(msg, "[g] #%u: YOU: %s", groupnumber, line); + new_lines(msg); + } else { + char msg[128]; + sprintf(msg, "[i] could not send message to group no. %u: %i", groupnumber, res); + new_lines(msg); + } + } + } else + new_lines("[i] invalid input: neither command nor in conversation"); } } -void wrap(char output[STRING_LENGTH], char input[STRING_LENGTH], int line_width) +/* basic wrap, ignores embedded '\t', '\n' or '|' + * inserts continuation markers if there's enough space left, + * otherwise turns spaces into newlines if possible */ +void wrap(char output[STRING_LENGTH_WRAPPED], char input[STRING_LENGTH], int line_width) { - strcpy(output, input); - size_t i, len = strlen(output); + size_t i, k, m, len = strlen(input); - for (i = line_width; i < len; i = i + line_width) { - while (output[i] != ' ' && i != 0) { - i--; + if ((line_width < 4) || (len < (size_t)line_width)) { + /* if line_width ridiculously tiny, it's not worth the effort */ + strcpy(output, input); + return; + } + + /* how much can we shift? */ + size_t delta_is = 0, delta_remain = STRING_LENGTH_WRAPPED - len - 1; + + /* if the line is very very short, don't insert continuation markers, + * as they would use up too much of the line */ + if ((size_t)line_width < 2 * wrap_cont_len) + delta_remain = 0; + + for (i = line_width; i < len; i += line_width) { + /* look backward for a space to expand/turn into a new line */ + k = i; + m = i - line_width; + + while (input[k] != ' ' && k > m) { + k--; } - if (i > 0) { - output[i] = '\n'; + if (k > m) { + if (delta_remain > wrap_cont_len) { + /* replace space with continuation, then + * set the pos. after the space as new line start + * (i.e. space is being "eaten") */ + memcpy(output + m + delta_is, input + m, k - m); + strcpy(output + k + delta_is, wrap_cont_str); + + delta_remain -= wrap_cont_len - 1; + delta_is += wrap_cont_len - 1; + i = k + 1; + } else { + /* no more space to push forward: replace the space, + * use its pos. + 1 as starting point for the next line */ + memcpy(output + m + delta_is, input + m, k - m); + output[k + delta_is] = '\n'; + i = k + 1; + } + } else { + /* string ends right here: + * don't add a continuation marker with nothing following */ + if (i == len - 1) + break; + + /* nothing found backwards */ + if (delta_remain > wrap_cont_len) { + /* break at the end of the line, + * i.e. in the middle of the word at the border */ + memcpy(output + m + delta_is, input + m, line_width); + strcpy(output + i + delta_is, wrap_cont_str); + + delta_remain -= wrap_cont_len; + delta_is += wrap_cont_len; + } else { + /* no more space to push, no space to convert: + * just copy the whole line and move on; + * means the line count calc'ed will be off */ + memcpy(output + m + delta_is, input + m, line_width); + } } } + + i -= line_width; + memcpy(output + i + delta_is, input + i, len - i); + + output[len + delta_is] = 0; +} + +/* + * extended wrap, honors '\n', accepts '|' as "break here when necessary" + * marks wrapped lines with "+ " in front, which does expand output + * does NOT honor '\t': would require a lot more work (and tab width isn't always 8) + */ +void wrap_bars(char output[STRING_LENGTH_WRAPPED], char input[STRING_LENGTH], size_t line_width) +{ + size_t len = strlen(input); + size_t ipos, opos = 0; + size_t bar_avail = 0, space_avail = 0, nl_got = 0; /* in opos */ + + for (ipos = 0; ipos < len; ipos++) { + if (opos - nl_got < line_width) { + /* not yet at the limit */ + char c = input[ipos]; + + if (c == ' ') + space_avail = opos; + + output[opos++] = input[ipos]; + + if (opos >= STRING_LENGTH_WRAPPED) { + opos = STRING_LENGTH_WRAPPED - 1; + break; + } + + if (c == '|') { + output[opos - 1] = ' '; + bar_avail = opos; + + if (opos + 2 >= STRING_LENGTH_WRAPPED) { + opos = STRING_LENGTH_WRAPPED - 1; + break; + } + + output[opos++] = '|'; + output[opos++] = ' '; + } + + if (c == '\n') + nl_got = opos; + + continue; + } else { + /* at the limit */ + if (bar_avail > nl_got) { + /* overwrite */ + memcpy(output + bar_avail - 1, wrap_cont_str, wrap_cont_len); + nl_got = bar_avail; + + ipos--; + continue; + } + + if (space_avail > nl_got) { + if (opos + wrap_cont_len - 1 >= STRING_LENGTH_WRAPPED) { + opos = STRING_LENGTH_WRAPPED - 1; + break; + } + + /* move forward by 2 characters */ + memmove(output + space_avail + 3, output + space_avail + 1, opos - (space_avail + 1)); + memcpy(output + space_avail, wrap_cont_str, wrap_cont_len); + nl_got = space_avail + 1; + + opos += 2; + ipos--; + continue; + } + + char c = input[ipos]; + + if ((c == '|') || (c == ' ') || (c == '\n')) { + if (opos + wrap_cont_len >= STRING_LENGTH_WRAPPED) { + opos = STRING_LENGTH_WRAPPED - 1; + break; + } + + memcpy(output + opos, wrap_cont_str, wrap_cont_len); + + nl_got = opos; + opos += wrap_cont_len; + } + + output[opos++] = input[ipos]; + + if (opos >= STRING_LENGTH_WRAPPED) { + opos = STRING_LENGTH_WRAPPED - 1; + break; + } + + continue; + } + } + + if (opos >= STRING_LENGTH_WRAPPED) + opos = STRING_LENGTH_WRAPPED - 1; + + output[opos] = 0; } int count_lines(char *string) @@ -497,18 +894,22 @@ char *appender(char *str, const char c) void do_refresh() { int count = 0; - char wrap_output[STRING_LENGTH]; + char wrap_output[STRING_LENGTH_WRAPPED]; int L; int i; for (i = 0; i < HISTORY; i++) { - wrap(wrap_output, lines[i], x); + if (flag[i]) + wrap_bars(wrap_output, lines[i], x); + else + wrap(wrap_output, lines[i], x); + L = count_lines(wrap_output); count = count + L; if (count < y) { move(y - 1 - count, 0); - printw(wrap_output); + printw("%s", wrap_output); clrtoeol(); } } @@ -516,7 +917,7 @@ void do_refresh() move(y - 1, 0); clrtoeol(); printw(">> "); - printw(input_line); + printw("%s", input_line); clrtoeol(); refresh(); } @@ -536,27 +937,39 @@ void print_request(uint8_t *public_key, uint8_t *data, uint16_t length, void *us void print_message(Tox *m, int friendnumber, uint8_t *string, uint16_t length, void *userdata) { - new_lines(format_message(m, (char *)string, friendnumber)); + /* ensure null termination */ + string[length - 1] = 0; + print_formatted_message(m, (char *)string, friendnumber, 0); } void print_nickchange(Tox *m, int friendnumber, uint8_t *string, uint16_t length, void *userdata) { - char name[TOX_MAX_NAME_LENGTH]; + char name[TOX_MAX_NAME_LENGTH + 1]; - if (tox_getname(m, friendnumber, (uint8_t *)name) != -1) { + if (getfriendname_terminated(m, friendnumber, name) != -1) { char msg[100 + length]; - sprintf(msg, "[i] [%d] %s is now known as %s.", friendnumber, name, string); + + if (name[0] != 0) + sprintf(msg, "[i] [%d] %s is now known as %s.", friendnumber, name, string); + else + sprintf(msg, "[i] [%d] Friend's name is %s.", friendnumber, string); + new_lines(msg); } } void print_statuschange(Tox *m, int friendnumber, uint8_t *string, uint16_t length, void *userdata) { - char name[TOX_MAX_NAME_LENGTH]; + char name[TOX_MAX_NAME_LENGTH + 1]; - if (tox_getname(m, friendnumber, (uint8_t *)name) != -1) { + if (getfriendname_terminated(m, friendnumber, name) != -1) { char msg[100 + length + strlen(name) + 1]; - sprintf(msg, "[i] [%d] %s's status changed to %s.", friendnumber, name, string); + + if (name[0] != 0) + sprintf(msg, "[i] [%d] %s's status changed to %s.", friendnumber, name, string); + else + sprintf(msg, "[i] [%d] Their status changed to %s.", friendnumber, string); + new_lines(msg); } } @@ -635,31 +1048,133 @@ static int load_data_or_init(Tox *m, char *path) return 0; } -void print_help(void) +void print_help(char *prog_name) { printf("nTox %.1f - Command-line tox-core client\n", 0.1); - puts("Options:"); - puts("\t-h\t-\tPrint this help and exit."); - puts("\t-f\t-\tSpecify a keyfile to read (or write to) from."); + printf("Usage: %s [--ipv4|--ipv6] IP PORT KEY [-f keyfile]\n", prog_name); + + puts("Options: (order IS relevant)"); + puts(" --ipv4 / --ipv6 [Optional] Support IPv4 only or IPv4 & IPv6."); + puts(" IP PORT KEY [REQUIRED] A server to connect to (IP/Port) and its key."); + puts(" -f keyfile [Optional] Specify a keyfile to read from and write to."); } void print_invite(Tox *m, int friendnumber, uint8_t *group_public_key, void *userdata) { char msg[256]; - sprintf(msg, "[i] recieved group chat invite from: %u, auto accepting and joining. group number: %u", friendnumber, + sprintf(msg, "[i] received group chat invite from: %u, auto accepting and joining. group number: %u", friendnumber, tox_join_groupchat(m, friendnumber, group_public_key)); new_lines(msg); } +void print_groupchatpeers(Tox *m, int groupnumber) +{ + int num = tox_group_number_peers(m, groupnumber); + + if (num < 0) + return; + + if (!num) { + new_lines("[g]+ no peers left in group."); + return; + } + + uint8_t names[num][TOX_MAX_NAME_LENGTH]; + tox_group_get_names(m, groupnumber, names, num); + int i; + char numstr[16]; + char header[] = "[g]+ "; + size_t header_len = strlen(header); + char msg[STRING_LENGTH]; + strcpy(msg, header); + size_t len_total = header_len; + + for (i = 0; i < num; ++i) { + size_t len_name = strlen((char *)names[i]); + size_t len_num = sprintf(numstr, "%i: ", i); + + if (len_num + len_name + len_total + 3 >= STRING_LENGTH) { + new_lines_mark(msg, 1); + + strcpy(msg, header); + len_total = header_len; + } + + strcpy(msg + len_total, numstr); + len_total += len_num; + strcpy(msg + len_total, (char *)names[i]); + len_total += len_name; + + if (i < num - 1) { + strcpy(msg + len_total, "|"); + len_total++; + } + } + + new_lines_mark(msg, 1); +} + void print_groupmessage(Tox *m, int groupnumber, int peernumber, uint8_t *message, uint16_t length, void *userdata) { char msg[256 + length]; uint8_t name[TOX_MAX_NAME_LENGTH]; - tox_group_peername(m, groupnumber, peernumber, name); - sprintf(msg, "[g] %u: <%s>: %s", groupnumber, name, message); + int len = tox_group_peername(m, groupnumber, peernumber, name); + + //print_groupchatpeers(m, groupnumber); + if (len <= 0) + name[0] = 0; + + if (name[0] != 0) + sprintf(msg, "[g] %u: %u <%s>: %s", groupnumber, peernumber, name, message); + else + sprintf(msg, "[g] #%u: %u Unknown: %s", groupnumber, peernumber, message); + new_lines(msg); } +void print_groupnamelistchange(Tox *m, int groupnumber, int peernumber, uint8_t change, void *userdata) +{ + char msg[256]; + if (change == TOX_CHAT_CHANGE_PEER_ADD) { + sprintf(msg, "[g] #%i: New peer %i.", groupnumber, peernumber); + new_lines(msg); + } else if (change == TOX_CHAT_CHANGE_PEER_DEL) { + /* if peer was the last in list, it simply dropped, + * otherwise it was overwritten by the last peer + * + * adjust output + */ + int peers_total = tox_group_number_peers(m, groupnumber); + + if (peers_total == peernumber) { + sprintf(msg, "[g] #%i: Peer %i left.", groupnumber, peernumber); + new_lines(msg); + } else { + uint8_t peername[TOX_MAX_NAME_LENGTH]; + int len = tox_group_peername(m, groupnumber, peernumber, peername); + + if (len <= 0) + peername[0] = 0; + + sprintf(msg, "[g] #%i: Peer %i left. Former peer [%i: <%s>] is now peer %i.", groupnumber, peernumber, + peers_total, peername, peernumber); + new_lines(msg); + } + } else if (change == TOX_CHAT_CHANGE_PEER_NAME) { + uint8_t peername[TOX_MAX_NAME_LENGTH]; + int len = tox_group_peername(m, groupnumber, peernumber, peername); + + if (len <= 0) + peername[0] = 0; + + sprintf(msg, "[g] #%i: Peer %i's name changed: %s", groupnumber, peernumber, peername); + new_lines(msg); + } else { + sprintf(msg, "[g] #%i: Name list changed (peer %i, change %i?):", groupnumber, peernumber, change); + new_lines(msg); + print_groupchatpeers(m, groupnumber); + } +} void file_request_accept(Tox *m, int friendnumber, uint8_t filenumber, uint64_t filesize, uint8_t *filename, uint16_t filename_length, void *userdata) { @@ -667,7 +1182,7 @@ void file_request_accept(Tox *m, int friendnumber, uint8_t filenumber, uint64_t sprintf(msg, "[t] %u is sending us: %s of size %llu", friendnumber, filename, (long long unsigned int)filesize); new_lines(msg); - if (tox_file_sendcontrol(m, friendnumber, 1, filenumber, 0, 0, 0)) { + if (tox_file_send_control(m, friendnumber, 1, filenumber, 0, 0, 0) == 0) { sprintf(msg, "Accepted file transfer. (saving file as: %u.%u.bin)", friendnumber, filenumber); new_lines(msg); } else @@ -696,7 +1211,7 @@ void write_file(Tox *m, int friendnumber, uint8_t filenumber, uint8_t *data, uin sprintf(filename, "%u.%u.bin", friendnumber, filenumber); FILE *pFile = fopen(filename, "a"); - if (tox_file_dataremaining(m, friendnumber, filenumber, 1) == 0) { + if (tox_file_data_remaining(m, friendnumber, filenumber, 1) == 0) { //file_control(m, friendnumber, 1, filenumber, 3, 0, 0); char msg[512]; sprintf(msg, "[t] %u file transfer: %u completed", friendnumber, filenumber); @@ -709,11 +1224,18 @@ void write_file(Tox *m, int friendnumber, uint8_t filenumber, uint8_t *data, uin fclose(pFile); } - int main(int argc, char *argv[]) { + /* minimalistic locale support (i.e. when printing dates) */ + setlocale(LC_ALL, ""); + if (argc < 4) { - printf("Usage: %s [--ipv4|--ipv6] IP PORT KEY [-f keyfile]\n", argv[0]); + if ((argc == 2) && !strcmp(argv[1], "-h")) { + print_help(argv[0]); + exit(0); + } + + printf("Usage: %s [--ipv4|--ipv6] IP PORT KEY [-f keyfile] (or %s -h for help)\n", argv[0], argv[0]); exit(0); } @@ -730,11 +1252,6 @@ int main(int argc, char *argv[]) char idstring[200] = {0}; Tox *m; - if ((argc == 2) && !strcmp(argv[1], "-h")) { - print_help(); - exit(0); - } - /* [-f keyfile] MUST be last two arguments, no point in walking over the list * especially not a good idea to accept it anywhere in the middle */ if (argc > argvoffset + 3) @@ -750,15 +1267,16 @@ int main(int argc, char *argv[]) load_data_or_init(m, filename); - tox_callback_friendrequest(m, print_request, NULL); - tox_callback_friendmessage(m, print_message, NULL); - tox_callback_namechange(m, print_nickchange, NULL); - tox_callback_statusmessage(m, print_statuschange, NULL); + tox_callback_friend_request(m, print_request, NULL); + tox_callback_friend_message(m, print_message, NULL); + tox_callback_name_change(m, print_nickchange, NULL); + tox_callback_status_message(m, print_statuschange, NULL); tox_callback_group_invite(m, print_invite, NULL); tox_callback_group_message(m, print_groupmessage, NULL); tox_callback_file_data(m, write_file, NULL); tox_callback_file_control(m, file_print_control, NULL); - tox_callback_file_sendrequest(m, file_request_accept, NULL); + tox_callback_file_send_request(m, file_request_accept, NULL); + tox_callback_group_namelist_change(m, print_groupnamelistchange, NULL); initscr(); noecho(); @@ -784,8 +1302,9 @@ int main(int argc, char *argv[]) nodelay(stdscr, TRUE); new_lines("[i] change username with /n"); - uint8_t name[TOX_MAX_NAME_LENGTH]; - uint16_t namelen = tox_getselfname(m, name, sizeof(name)); + uint8_t name[TOX_MAX_NAME_LENGTH + 1]; + uint16_t namelen = tox_get_self_name(m, name, sizeof(name)); + name[namelen] = 0; if (namelen > 0) { char whoami[128 + TOX_MAX_NAME_LENGTH]; @@ -795,6 +1314,16 @@ int main(int argc, char *argv[]) time_t timestamp0 = time(NULL); + uint8_t pollok = 0; + uint16_t len = 0; + + if (!tox_wait_prepare(m, NULL, &len)) + pollok = 1; + else + new_lines("[i] failed to setup for low cpu consumption"); + + uint8_t data[len]; + while (1) { if (on == 0) { if (tox_isconnected(m)) { @@ -810,9 +1339,20 @@ int main(int argc, char *argv[]) } } + if (numfilesenders > 0) + // during file transfer wasting cpu cycles is almost unavoidable + c_sleep(1); + else { + if (pollok && (tox_wait_prepare(m, data, &len) == 1)) { + /* 250ms is more than fast enough in "regular" mode */ + tox_wait_execute(m, data, len, 100); + tox_wait_cleanup(m, data, len); + } else + c_sleep(25); + } + send_filesenders(m); tox_do(m); - c_sleep(1); do_refresh(); c = getch(); diff --git a/testing/nTox.h b/testing/nTox.h index a72ce0c2..44def142 100644 --- a/testing/nTox.h +++ b/testing/nTox.h @@ -24,24 +24,19 @@ #ifndef NTOX_H #define NTOX_H -#include -#include -#include -#include +/* + * module actually exports nothing for the outside + */ + #include +#include #include "../toxcore/tox.h" #define STRING_LENGTH 256 #define HISTORY 50 -#define PUB_KEY_BYTES 32 -uint32_t resolve_addr(const char *address); void new_lines(char *line); -void line_eval(Tox *m, char *line); -void wrap(char output[STRING_LENGTH], char input[STRING_LENGTH], int line_width) ; -int count_lines(char *string) ; -char *appender(char *str, const char c); void do_refresh(); #endif diff --git a/testing/tox_sync.c b/testing/tox_sync.c index 3207e297..c00189aa 100644 --- a/testing/tox_sync.c +++ b/testing/tox_sync.c @@ -62,11 +62,11 @@ void send_filesenders(Tox *m) continue; while (1) { - if (!tox_file_senddata(m, file_senders[i].friendnum, file_senders[i].filenumber, file_senders[i].nextpiece, + if (!tox_file_send_data(m, file_senders[i].friendnum, file_senders[i].filenumber, file_senders[i].nextpiece, file_senders[i].piecelength)) break; - file_senders[i].piecelength = fread(file_senders[i].nextpiece, 1, tox_filedata_size(m, file_senders[i].friendnum), + file_senders[i].piecelength = fread(file_senders[i].nextpiece, 1, tox_file_data_size(m, file_senders[i].friendnum), file_senders[i].file); if (file_senders[i].piecelength == 0) { @@ -74,7 +74,7 @@ void send_filesenders(Tox *m) file_senders[i].file = 0; printf("[t] %u file transfer: %u completed %i\n", file_senders[i].friendnum, file_senders[i].filenumber, - tox_file_sendcontrol(m, file_senders[i].friendnum, 0, file_senders[i].filenumber, TOX_FILECONTROL_FINISHED, 0, 0)); + tox_file_send_control(m, file_senders[i].friendnum, 0, file_senders[i].filenumber, TOX_FILECONTROL_FINISHED, 0, 0)); break; } } @@ -90,13 +90,13 @@ int add_filesender(Tox *m, uint16_t friendnum, char *filename) fseek(tempfile, 0, SEEK_END); uint64_t filesize = ftell(tempfile); fseek(tempfile, 0, SEEK_SET); - int filenum = tox_new_filesender(m, friendnum, filesize, (uint8_t *)filename, strlen(filename) + 1); + int filenum = tox_new_file_sender(m, friendnum, filesize, (uint8_t *)filename, strlen(filename) + 1); if (filenum == -1) return -1; file_senders[numfilesenders].file = tempfile; - file_senders[numfilesenders].piecelength = fread(file_senders[numfilesenders].nextpiece, 1, tox_filedata_size(m, + file_senders[numfilesenders].piecelength = fread(file_senders[numfilesenders].nextpiece, 1, tox_file_data_size(m, file_senders[numfilesenders].friendnum), file_senders[numfilesenders].file); file_senders[numfilesenders].friendnum = friendnum; @@ -149,18 +149,18 @@ void file_request_accept(Tox *m, int friendnumber, uint8_t filenumber, uint64_t if (tempfile != 0) { fclose(tempfile); - tox_file_sendcontrol(m, friendnumber, 1, filenumber, TOX_FILECONTROL_KILL, 0, 0); + tox_file_send_control(m, friendnumber, 1, filenumber, TOX_FILECONTROL_KILL, 0, 0); return; } file_recv[filenumber].file = fopen(fullpath, "wb"); if (file_recv[filenumber].file == 0) { - tox_file_sendcontrol(m, friendnumber, 1, filenumber, TOX_FILECONTROL_KILL, 0, 0); + tox_file_send_control(m, friendnumber, 1, filenumber, TOX_FILECONTROL_KILL, 0, 0); return; } - if (tox_file_sendcontrol(m, friendnumber, 1, filenumber, TOX_FILECONTROL_ACCEPT, 0, 0)) { + if (tox_file_send_control(m, friendnumber, 1, filenumber, TOX_FILECONTROL_ACCEPT, 0, 0)) { printf("Accepted file transfer. (file: %s)\n", fullpath); } @@ -215,8 +215,8 @@ int main(int argc, char *argv[]) Tox *tox = tox_new(ipv6enabled); tox_callback_file_data(tox, write_file, NULL); tox_callback_file_control(tox, file_print_control, NULL); - tox_callback_file_sendrequest(tox, file_request_accept, NULL); - tox_callback_connectionstatus(tox, print_online, NULL); + tox_callback_file_send_request(tox, file_request_accept, NULL); + tox_callback_connection_status(tox, print_online, NULL); uint16_t port = htons(atoi(argv[argvoffset + 2])); unsigned char *binary_string = hex_string_to_bin(argv[argvoffset + 3]); @@ -228,7 +228,7 @@ int main(int argc, char *argv[]) } uint8_t address[TOX_FRIEND_ADDRESS_SIZE]; - tox_getaddress(tox, address); + tox_get_address(tox, address); uint32_t i; for (i = 0; i < TOX_FRIEND_ADDRESS_SIZE; i++) { @@ -242,7 +242,7 @@ int main(int argc, char *argv[]) return 1; } - int num = tox_addfriend(tox, hex_string_to_bin(temp_id), (uint8_t *)"Install Gentoo", sizeof("Install Gentoo")); + int num = tox_add_friend(tox, hex_string_to_bin(temp_id), (uint8_t *)"Install Gentoo", sizeof("Install Gentoo")); if (num < 0) { printf("\nSomething went wrong when adding friend.\n"); @@ -260,7 +260,7 @@ int main(int argc, char *argv[]) notconnected = 0; } - if (not_sending() && tox_get_friend_connectionstatus(tox, num)) { + if (not_sending() && tox_get_friend_connection_status(tox, num)) { d = opendir(path); if (d) { diff --git a/toxcore/DHT.c b/toxcore/DHT.c index 5f4aa228..242cbea2 100644 --- a/toxcore/DHT.c +++ b/toxcore/DHT.c @@ -28,34 +28,27 @@ #endif #include "DHT.h" -#include "network.h" +#include "assoc.h" #include "ping.h" + +#include "network.h" +#include "LAN_discovery.h" #include "misc_tools.h" #include "util.h" -#include "LAN_discovery.h" - -/* The number of seconds for a non responsive node to become bad. */ -#define BAD_NODE_TIMEOUT 70 /* The max number of nodes to send with send nodes. */ #define MAX_SENT_NODES 8 -/* Ping timeout in seconds */ -#define PING_TIMEOUT 3 - /* The timeout after which a node is discarded completely. */ #define KILL_NODE_TIMEOUT 300 -/* Ping interval in seconds for each node in our lists. */ -#define PING_INTERVAL 60 - /* Ping interval in seconds for each random sending of a get nodes request. */ #define GET_NODE_INTERVAL 5 -#define MAX_PUNCHING_PORTS 128 +#define MAX_PUNCHING_PORTS 32 /* Interval in seconds between punching attempts*/ -#define PUNCH_INTERVAL 10 +#define PUNCH_INTERVAL 3 #define NAT_PING_REQUEST 0 #define NAT_PING_RESPONSE 1 @@ -115,21 +108,6 @@ static int client_id_cmp(ClientPair p1, ClientPair p2) return c; } -static int client_in_list(Client_data *list, uint32_t length, uint8_t *client_id) -{ - uint32_t i; - - for (i = 0; i < length; i++) - - /* Dead nodes are considered dead (not in the list)*/ - if (!is_timeout(list[i].assoc4.timestamp, KILL_NODE_TIMEOUT) || - !is_timeout(list[i].assoc6.timestamp, KILL_NODE_TIMEOUT)) - if (id_equal(list[i].client_id, client_id)) - return 1; - - return 0; -} - /* Check if client with client_id is already in list of length length. * If it is then set its corresponding timestamp to current time. * If the id is already in the list with a different ip_port, update it. @@ -373,8 +351,7 @@ static void get_close_nodes_inner(DHT *dht, uint8_t *client_id, Node_format *nod * * want_good : do we want only good nodes as checked with the hardening returned or not? */ -static uint32_t get_close_nodes(DHT *dht, uint8_t *client_id, Node_format *nodes_list, sa_family_t sa_family, - uint8_t is_LAN, uint8_t want_good) +static int get_somewhat_close_nodes(DHT *dht, uint8_t *client_id, Node_format *nodes_list, sa_family_t sa_family, uint8_t is_LAN, uint8_t want_good) { uint32_t num_nodes = 0, i; get_close_nodes_inner(dht, client_id, nodes_list, sa_family, @@ -388,6 +365,53 @@ static uint32_t get_close_nodes(DHT *dht, uint8_t *client_id, Node_format *nodes return num_nodes; } +static int get_close_nodes(DHT *dht, uint8_t *client_id, Node_format *nodes_list, sa_family_t sa_family, uint8_t is_LAN, uint8_t want_good) +{ + if (!dht->assoc) + return get_somewhat_close_nodes(dht, client_id, nodes_list, sa_family, is_LAN, want_good); + + Client_data *result[MAX_SENT_NODES]; + + Assoc_close_entries request; + memset(&request, 0, sizeof(request)); + request.count = MAX_SENT_NODES; + request.count_good = MAX_SENT_NODES; + request.result = result; + request.wanted_id = client_id; + request.flags = (is_LAN ? LANOk : 0) + (sa_family == AF_INET ? ProtoIPv4 : ProtoIPv6); + + uint8_t num_found = Assoc_get_close_entries(dht->assoc, &request); + + if (!num_found) + return get_somewhat_close_nodes(dht, client_id, nodes_list, sa_family, is_LAN, want_good); + + uint8_t i, num_returned = 0; + + for (i = 0; i < num_found; i++) { + Client_data *client = result[i]; + + if (client) { + id_copy(nodes_list[i].client_id, client->client_id); + + if (sa_family == AF_INET) + if (ipport_isset(&client->assoc4.ip_port)) { + nodes_list[i].ip_port = client->assoc4.ip_port; + num_returned++; + continue; + } + + if (sa_family == AF_INET6) + if (ipport_isset(&client->assoc6.ip_port)) { + nodes_list[i].ip_port = client->assoc6.ip_port; + num_returned++; + continue; + } + } + } + + return num_returned; +} + /* Replace first bad (or empty) node with this one. * * return 0 if successful. @@ -406,21 +430,31 @@ static int replace_bad( Client_data *list, for (i = 0; i < length; ++i) { /* If node is bad */ Client_data *client = &list[i]; - IPPTsPng *ipptp = NULL; - if (ip_port.ip.family == AF_INET) - ipptp = &client->assoc4; - else - ipptp = &client->assoc6; + if (is_timeout(client->assoc4.timestamp, BAD_NODE_TIMEOUT) && + is_timeout(client->assoc6.timestamp, BAD_NODE_TIMEOUT)) { + + IPPTsPng *ipptp_write = NULL; + IPPTsPng *ipptp_clear = NULL; + + if (ip_port.ip.family == AF_INET) { + ipptp_write = &client->assoc4; + ipptp_clear = &client->assoc6; + } else { + ipptp_write = &client->assoc6; + ipptp_clear = &client->assoc4; + } - if (is_timeout(ipptp->timestamp, BAD_NODE_TIMEOUT)) { memcpy(client->client_id, client_id, CLIENT_ID_SIZE); - ipptp->ip_port = ip_port; - ipptp->timestamp = unix_time(); + ipptp_write->ip_port = ip_port; + ipptp_write->timestamp = unix_time(); - ip_reset(&ipptp->ret_ip_port.ip); - ipptp->ret_ip_port.port = 0; - ipptp->ret_timestamp = 0; + ip_reset(&ipptp_write->ret_ip_port.ip); + ipptp_write->ret_ip_port.port = 0; + ipptp_write->ret_timestamp = 0; + + /* zero out other address */ + memset(ipptp_clear, 0, sizeof(*ipptp_clear)); return 0; } @@ -451,7 +485,9 @@ static void sort_list(Client_data *list, uint32_t length, uint8_t *comp_client_i list[i] = pairs[i].c2; } -/* Replace the first good node that is further to the comp_client_id than that of the client_id in the list */ +/* Replace the first good node that is further to the comp_client_id than that of the client_id in the list + * + * returns 0 when the item was stored, 1 otherwise */ static int replace_good( Client_data *list, uint32_t length, uint8_t *client_id, @@ -482,20 +518,28 @@ static int replace_good( Client_data *list, assert(replace >= 0 && replace < length); #endif Client_data *client = &list[replace]; - IPPTsPng *ipptp = NULL; + IPPTsPng *ipptp_write = NULL; + IPPTsPng *ipptp_clear = NULL; - if (ip_port.ip.family == AF_INET) - ipptp = &client->assoc4; - else - ipptp = &client->assoc6; + if (ip_port.ip.family == AF_INET) { + ipptp_write = &client->assoc4; + ipptp_clear = &client->assoc6; + } else { + ipptp_write = &client->assoc6; + ipptp_clear = &client->assoc4; + } memcpy(client->client_id, client_id, CLIENT_ID_SIZE); - ipptp->ip_port = ip_port; - ipptp->timestamp = unix_time(); + ipptp_write->ip_port = ip_port; + ipptp_write->timestamp = unix_time(); + + ip_reset(&ipptp_write->ret_ip_port.ip); + ipptp_write->ret_ip_port.port = 0; + ipptp_write->ret_timestamp = 0; + + /* zero out other address */ + memset(ipptp_clear, 0, sizeof(*ipptp_clear)); - ip_reset(&ipptp->ret_ip_port.ip); - ipptp->ret_ip_port.port = 0; - ipptp->ret_timestamp = 0; return 0; } @@ -504,10 +548,12 @@ static int replace_good( Client_data *list, /* Attempt to add client with ip_port and client_id to the friends client list * and close_clientlist. + * + * returns 1+ if the item is used in any list, 0 else */ -void addto_lists(DHT *dht, IP_Port ip_port, uint8_t *client_id) +int addto_lists(DHT *dht, IP_Port ip_port, uint8_t *client_id) { - uint32_t i; + uint32_t i, used = 0; /* convert IPv4-in-IPv6 to IPv4 */ if ((ip_port.ip.family == AF_INET6) && IN6_IS_ADDR_V4MAPPED(&ip_port.ip.ip6.in6_addr)) { @@ -521,10 +567,13 @@ void addto_lists(DHT *dht, IP_Port ip_port, uint8_t *client_id) if (!client_or_ip_port_in_list(dht->close_clientlist, LCLIENT_LIST, client_id, ip_port)) { if (replace_bad(dht->close_clientlist, LCLIENT_LIST, client_id, ip_port)) { /* If we can't replace bad nodes we try replacing good ones. */ - replace_good(dht->close_clientlist, LCLIENT_LIST, client_id, ip_port, - dht->c->self_public_key); - } - } + if (!replace_good(dht->close_clientlist, LCLIENT_LIST, client_id, ip_port, + dht->c->self_public_key)) + used++; + } else + used++; + } else + used++; for (i = 0; i < dht->num_friends; ++i) { if (!client_or_ip_port_in_list(dht->friends_list[i].client_list, @@ -533,17 +582,22 @@ void addto_lists(DHT *dht, IP_Port ip_port, uint8_t *client_id) if (replace_bad(dht->friends_list[i].client_list, MAX_FRIEND_CLIENTS, client_id, ip_port)) { /* If we can't replace bad nodes we try replacing good ones. */ - replace_good(dht->friends_list[i].client_list, MAX_FRIEND_CLIENTS, - client_id, ip_port, dht->friends_list[i].client_id); - } - } + if (!replace_good(dht->friends_list[i].client_list, MAX_FRIEND_CLIENTS, + client_id, ip_port, dht->friends_list[i].client_id)) + used++; + } else + used++; + } else + used++; } + + return used; } /* If client_id is a friend or us, update ret_ip_port * nodeclient_id is the id of the node that sent us this info. */ -static void returnedip_ports(DHT *dht, IP_Port ip_port, uint8_t *client_id, uint8_t *nodeclient_id) +static int returnedip_ports(DHT *dht, IP_Port ip_port, uint8_t *client_id, uint8_t *nodeclient_id) { uint32_t i, j; uint64_t temp_time = unix_time(); @@ -565,10 +619,9 @@ static void returnedip_ports(DHT *dht, IP_Port ip_port, uint8_t *client_id, uint dht->close_clientlist[i].assoc6.ret_timestamp = temp_time; } - return; + return 1; } } - } else { for (i = 0; i < dht->num_friends; ++i) { if (id_equal(client_id, dht->friends_list[i].client_id)) { @@ -582,13 +635,14 @@ static void returnedip_ports(DHT *dht, IP_Port ip_port, uint8_t *client_id, uint dht->friends_list[i].client_list[j].assoc6.ret_timestamp = temp_time; } - return; + return 1; } } } } - } + + return 0; } #define NODES_ENCRYPTED_MESSAGE_LENGTH (crypto_secretbox_NONCEBYTES + sizeof(uint64_t) + sizeof(Node_format) + sizeof(Node_format) + crypto_secretbox_MACBYTES) @@ -870,7 +924,16 @@ static int handle_sendnodes_core(void *object, IP_Port source, uint8_t *packet, return 1; /* store the address the *request* was sent to */ - addto_lists(dht, source, packet + 1); + int used = addto_lists(dht, source, packet + 1); + + if (dht->assoc) { + IPPTs ippts; + + ippts.ip_port = source; + ippts.timestamp = unix_time(); + + Assoc_add_entry(dht->assoc, packet + 1, &ippts, &source, used ? 1 : 0); + } *num_nodes_out = num_nodes; @@ -890,24 +953,30 @@ static int handle_sendnodes(void *object, IP_Port source, uint8_t *packet, uint3 &sendback_node)) return 1; - Node4_format *nodes4_list = (Node4_format *)(plain); - uint32_t i; - IP_Port ipp; - ipp.ip.family = AF_INET; + Node4_format *nodes4_list = (Node4_format *)(plain); + + uint64_t time_now = unix_time(); + IPPTs ippts; + ippts.ip_port.ip.family = AF_INET; + ippts.timestamp = time_now; + + uint32_t i; Node_format nodes_list[MAX_SENT_NODES]; for (i = 0; i < num_nodes; i++) if ((nodes4_list[i].ip_port.ip.uint32 != 0) && (nodes4_list[i].ip_port.ip.uint32 != (uint32_t)~0)) { - ipp.ip.ip4.uint32 = nodes4_list[i].ip_port.ip.uint32; - ipp.port = nodes4_list[i].ip_port.port; + ippts.ip_port.ip.ip4.uint32 = nodes4_list[i].ip_port.ip.uint32; + ippts.ip_port.port = nodes4_list[i].ip_port.port; - send_ping_request(dht->ping, ipp, nodes4_list[i].client_id); - returnedip_ports(dht, ipp, nodes4_list[i].client_id, packet + 1); + send_ping_request(dht->ping, ippts.ip_port, nodes4_list[i].client_id); + int used = returnedip_ports(dht, ippts.ip_port, nodes4_list[i].client_id, packet + 1); memcpy(nodes_list[i].client_id, nodes4_list[i].client_id, CLIENT_ID_SIZE); - ipport_copy(&nodes_list[i].ip_port, &ipp); + ipport_copy(&nodes_list[i].ip_port, &ippts.ip_port); + if (dht->assoc) + Assoc_add_entry(dht->assoc, nodes4_list[i].client_id, &ippts, NULL, used ? 1 : 0); } send_hardening_getnode_res(dht, &sendback_node, packet + 1, nodes_list, num_nodes); @@ -928,13 +997,22 @@ static int handle_sendnodes_ipv6(void *object, IP_Port source, uint8_t *packet, return 1; Node_format *nodes_list = (Node_format *)(plain); + uint64_t time_now = unix_time(); uint32_t i; send_hardening_getnode_res(dht, &sendback_node, packet + 1, nodes_list, num_nodes); for (i = 0; i < num_nodes; i++) if (ipport_isset(&nodes_list[i].ip_port)) { send_ping_request(dht->ping, nodes_list[i].ip_port, nodes_list[i].client_id); - returnedip_ports(dht, nodes_list[i].ip_port, nodes_list[i].client_id, packet + 1); + int used = returnedip_ports(dht, nodes_list[i].ip_port, nodes_list[i].client_id, packet + 1); + + if (dht->assoc) { + IPPTs ippts; + ippts.ip_port = nodes_list[i].ip_port; + ippts.timestamp = time_now; + + Assoc_add_entry(dht->assoc, nodes_list[i].client_id, &ippts, NULL, used ? 1 : 0); + } } return 0; @@ -983,7 +1061,38 @@ int DHT_addfriend(DHT *dht, uint8_t *client_id) dht->friends_list[dht->num_friends].nat.NATping_id = ((uint64_t)random_int() << 32) + random_int(); ++dht->num_friends; - get_bunchnodes(dht, dht->close_clientlist, LCLIENT_LIST, MAX_FRIEND_CLIENTS, client_id);/*TODO: make this better?*/ + + if (dht->assoc) { + /* get up to MAX_FRIEND_CLIENTS connectable nodes */ + DHT_Friend *friend = &dht->friends_list[dht->num_friends - 1]; + + Assoc_close_entries close_entries; + memset(&close_entries, 0, sizeof(close_entries)); + close_entries.wanted_id = client_id; + close_entries.count_good = MAX_FRIEND_CLIENTS / 2; + close_entries.count = MAX_FRIEND_CLIENTS; + close_entries.result = calloc(MAX_FRIEND_CLIENTS, sizeof(*close_entries.result)); + + uint8_t i, found = Assoc_get_close_entries(dht->assoc, &close_entries); + + for (i = 0; i < found; i++) + memcpy(&friend->client_list[i], close_entries.result[i], sizeof(*close_entries.result[i])); + + if (found) { + /* send getnodes to the "best" entry */ + Client_data *client = &friend->client_list[0]; + + if (ipport_isset(&client->assoc4.ip_port)) + getnodes(dht, client->assoc4.ip_port, client->client_id, friend->client_id, NULL); + + if (ipport_isset(&client->assoc6.ip_port)) + getnodes(dht, client->assoc6.ip_port, client->client_id, friend->client_id, NULL); + } + } + + /*TODO: make this better?*/ + get_bunchnodes(dht, dht->close_clientlist, LCLIENT_LIST, MAX_FRIEND_CLIENTS, client_id); + return 0; } @@ -1055,10 +1164,12 @@ int DHT_getfriendip(DHT *dht, uint8_t *client_id, IP_Port *ip_port) return -1; } -static void do_ping_and_sendnode_requests(DHT *dht, uint64_t *lastgetnode, uint8_t *client_id, +/* returns number of nodes not in kill-timeout */ +static uint8_t do_ping_and_sendnode_requests(DHT *dht, uint64_t *lastgetnode, uint8_t *client_id, Client_data *list, uint32_t list_count) { uint32_t i; + uint8_t not_kill = 0; uint64_t temp_time = unix_time(); uint32_t num_nodes = 0; @@ -1073,6 +1184,8 @@ static void do_ping_and_sendnode_requests(DHT *dht, uint64_t *lastgetnode, uint8 for (a = 0, assoc = &client->assoc6; a < 2; a++, assoc = &client->assoc4) if (!is_timeout(assoc->timestamp, KILL_NODE_TIMEOUT)) { + not_kill++; + if (is_timeout(assoc->last_pinged, PING_INTERVAL)) { send_ping_request(dht->ping, assoc->ip_port, client->client_id ); assoc->last_pinged = temp_time; @@ -1093,6 +1206,8 @@ static void do_ping_and_sendnode_requests(DHT *dht, uint64_t *lastgetnode, uint8 client_id, NULL); *lastgetnode = temp_time; } + + return not_kill; } /* Ping each client in the "friends" list every PING_INTERVAL seconds. Send a get nodes request @@ -1112,12 +1227,40 @@ static void do_DHT_friends(DHT *dht) */ static void do_Close(DHT *dht) { - do_ping_and_sendnode_requests(dht, &dht->close_lastgetnodes, dht->c->self_public_key, - dht->close_clientlist, LCLIENT_LIST); + uint8_t not_killed = do_ping_and_sendnode_requests(dht, &dht->close_lastgetnodes, dht->c->self_public_key, + dht->close_clientlist, LCLIENT_LIST); + + if (!not_killed) { + /* all existing nodes are at least KILL_NODE_TIMEOUT, + * which means we are mute, as we only send packets to + * nodes NOT in KILL_NODE_TIMEOUT + * + * so: reset all nodes to be BAD_NODE_TIMEOUT, but not + * KILL_NODE_TIMEOUT, so we at least keep trying pings */ + uint64_t badonly = unix_time() - BAD_NODE_TIMEOUT; + size_t i, a; + + for (i = 0; i < LCLIENT_LIST; i++) { + Client_data *client = &dht->close_clientlist[i]; + IPPTsPng *assoc; + + for (a = 0, assoc = &client->assoc4; a < 2; a++, assoc = &client->assoc6) + if (assoc->timestamp) + assoc->timestamp = badonly; + } + } } void DHT_bootstrap(DHT *dht, IP_Port ip_port, uint8_t *public_key) { + if (dht->assoc) { + IPPTs ippts; + ippts.ip_port = ip_port; + ippts.timestamp = 0; + + Assoc_add_entry(dht->assoc, public_key, &ippts, NULL, 0); + } + getnodes(dht, ip_port, public_key, dht->c->self_public_key, NULL); } int DHT_bootstrap_from_address(DHT *dht, const char *address, uint8_t ipv6enabled, @@ -1191,30 +1334,25 @@ static int friend_iplist(DHT *dht, IP_Port *ip_portlist, uint16_t friend_num) int num_ipv4s = 0; IP_Port ipv6s[MAX_FRIEND_CLIENTS]; int num_ipv6s = 0; - uint8_t connected; int i; for (i = 0; i < MAX_FRIEND_CLIENTS; ++i) { client = &(friend->client_list[i]); - connected = 0; /* If ip is not zero and node is good. */ if (ip_isset(&client->assoc4.ret_ip_port.ip) && !is_timeout(client->assoc4.ret_timestamp, BAD_NODE_TIMEOUT)) { ipv4s[num_ipv4s] = client->assoc4.ret_ip_port; ++num_ipv4s; - - connected = 1; } if (ip_isset(&client->assoc6.ret_ip_port.ip) && !is_timeout(client->assoc6.ret_timestamp, BAD_NODE_TIMEOUT)) { ipv6s[num_ipv6s] = client->assoc6.ret_ip_port; ++num_ipv6s; - - connected = 1; } - if (connected && id_equal(client->client_id, friend->client_id)) - return 0; /* direct connectivity */ + if (id_equal(client->client_id, friend->client_id)) + if (!is_timeout(client->assoc6.timestamp, BAD_NODE_TIMEOUT) || !is_timeout(client->assoc4.timestamp, BAD_NODE_TIMEOUT)) + return 0; /* direct connectivity */ } #ifdef FRIEND_IPLIST_PAD @@ -1498,7 +1636,7 @@ static void punch_holes(DHT *dht, IP ip, uint16_t *port_list, uint16_t numports, uint16_t firstport = port_list[0]; for (i = 0; i < numports; ++i) { - if (firstport != port_list[0]) + if (firstport != port_list[i]) break; } @@ -1844,6 +1982,8 @@ DHT *new_DHT(Net_Crypto *c) cryptopacket_registerhandler(c, CRYPTO_PACKET_HARDENING, &handle_hardening, dht); new_symmetric_key(dht->secret_symmetric_key); + dht->assoc = new_Assoc_default(dht->c->self_public_key); + return dht; } @@ -1851,14 +1991,20 @@ void do_DHT(DHT *dht) { unix_time_update(); + if (dht->last_run == unix_time()) { + return; + } + do_Close(dht); do_DHT_friends(dht); do_NAT(dht); do_toping(dht->ping); do_hardening(dht); + dht->last_run = unix_time(); } void kill_DHT(DHT *dht) { + kill_Assoc(dht->assoc); kill_ping(dht->ping); free(dht->friends_list); free(dht); diff --git a/toxcore/DHT.h b/toxcore/DHT.h index f4651515..f82d9d76 100644 --- a/toxcore/DHT.h +++ b/toxcore/DHT.h @@ -38,6 +38,22 @@ /* Maximum newly announced nodes to ping per TIME_TOPING seconds. */ #define MAX_TOPING 16 +/* Ping timeout in seconds */ +#define PING_TIMEOUT 3 + +/* Ping interval in seconds for each node in our lists. */ +#define PING_INTERVAL 60 + +/* The number of seconds for a non responsive node to become bad. */ +#define PINGS_MISSED_NODE_GOES_BAD 3 +#define PING_ROUNDTRIP 2 +#define BAD_NODE_TIMEOUT (PING_INTERVAL + PINGS_MISSED_NODE_GOES_BAD * PING_INTERVAL + PING_ROUNDTRIP) + +typedef struct { + IP_Port ip_port; + uint64_t timestamp; +} IPPTs; + typedef struct { /* Node routes request correctly (true (1) or false/didn't check (0)) */ uint8_t routes_requests_ok; @@ -113,21 +129,23 @@ typedef struct { typedef struct { Net_Crypto *c; - Client_data close_clientlist[LCLIENT_LIST]; - DHT_Friend *friends_list; - uint16_t num_friends; - uint64_t close_lastgetnodes; - - void *ping; + Client_data close_clientlist[LCLIENT_LIST]; + uint64_t close_lastgetnodes; /* Note: this key should not be/is not used to transmit any sensitive materials */ uint8_t secret_symmetric_key[crypto_secretbox_KEYBYTES]; + + DHT_Friend *friends_list; + uint16_t num_friends; + + struct PING *ping; + + struct Assoc *assoc; + + uint64_t last_run; } DHT; /*----------------------------------------------------------------------------------*/ - -Client_data *DHT_get_close_list(DHT *dht); - /* Add a new friend to the friends list. * client_id must be CLIENT_ID_SIZE bytes long. * @@ -247,7 +265,7 @@ void kill_DHT(DHT *dht); */ int DHT_isconnected(DHT *dht); -void addto_lists(DHT *dht, IP_Port ip_port, uint8_t *client_id); - +int addto_lists(DHT *dht, IP_Port ip_port, uint8_t *client_id); #endif + diff --git a/toxcore/LAN_discovery.c b/toxcore/LAN_discovery.c index eb0b95a1..eadec9ec 100644 --- a/toxcore/LAN_discovery.c +++ b/toxcore/LAN_discovery.c @@ -109,7 +109,7 @@ static uint32_t send_broadcasts(Networking_Core *net, uint16_t port, uint8_t *da int i; for (i = 0; i < broadcast_count; i++) - sendpacket(net, broadcast_ip_port[i], data, 1 + crypto_box_PUBLICKEYBYTES); + sendpacket(net, broadcast_ip_port[i], data, length); return 1; } @@ -176,6 +176,11 @@ int LAN_ip(IP ip) && ip4.uint8[2] != 255) return 0; + /* RFC 6598: 100.64.0.0 to 100.127.255.255 (100.64.0.0/10) + * (shared address space to stack another layer of NAT) */ + if ((ip4.uint8[0] == 100) && ((ip4.uint8[1] & 0xC0) == 0x40)) + return 0; + } else if (ip.family == AF_INET6) { /* autogenerated for each interface: FE80::* (up to FEBF::*) @@ -191,6 +196,10 @@ int LAN_ip(IP ip) ip4.ip4.uint32 = ip.ip6.uint32[3]; return LAN_ip(ip4); } + + /* localhost in IPv6 (::1) */ + if (IN6_IS_ADDR_LOOPBACK(&ip.ip6.in6_addr)) + return 0; } return -1; diff --git a/toxcore/Makefile.inc b/toxcore/Makefile.inc index 116a3e29..8208c548 100644 --- a/toxcore/Makefile.inc +++ b/toxcore/Makefile.inc @@ -27,6 +27,8 @@ libtoxcore_la_SOURCES = ../toxcore/DHT.h \ ../toxcore/util.c \ ../toxcore/group_chats.h \ ../toxcore/group_chats.c \ + ../toxcore/assoc.h \ + ../toxcore/assoc.c \ ../toxcore/misc_tools.h libtoxcore_la_CFLAGS = -I$(top_srcdir) \ diff --git a/toxcore/Messenger.c b/toxcore/Messenger.c index 08e3c70c..32234784 100644 --- a/toxcore/Messenger.c +++ b/toxcore/Messenger.c @@ -26,9 +26,11 @@ #endif #include "Messenger.h" +#include "assoc.h" #include "network.h" #include "util.h" + #define MIN(a,b) (((a)<(b))?(a):(b)) @@ -435,6 +437,10 @@ int setname(Messenger *m, uint8_t *name, uint16_t length) for (i = 0; i < m->numfriends; ++i) m->friendlist[i].name_sent = 0; + for (i = 0; i < m->numchats; i++) + if (m->chats[i] != NULL) + set_nick(m->chats[i], name, length); /* TODO: remove this (group nicks should not be tied to the global one) */ + return 0; } @@ -701,6 +707,23 @@ int write_cryptpacket_id(Messenger *m, int friendnumber, uint8_t packet_id, uint /**********GROUP CHATS************/ +/* return 1 if the groupnumber is not valid. + * return 0 if the groupnumber is valid. + */ +static uint8_t groupnumber_not_valid(Messenger *m, int groupnumber) +{ + if ((unsigned int)groupnumber >= m->numchats) + return 1; + + if (m->chats == NULL) + return 1; + + if (m->chats[groupnumber] == NULL) + return 1; + return 0; +} + + /* returns valid ip port of connected friend on success * returns zeroed out IP_Port on failure */ @@ -728,8 +751,9 @@ static int group_num(Messenger *m, uint8_t *group_public_key) uint32_t i; for (i = 0; i < m->numchats; ++i) { - if (id_equal(m->chats[i]->self_public_key, group_public_key)) - return i; + if (m->chats[i] != NULL) + if (id_equal(m->chats[i]->self_public_key, group_public_key)) + return i; } return -1; @@ -755,20 +779,51 @@ void m_callback_group_message(Messenger *m, void (*function)(Messenger *m, int, m->group_message = function; m->group_message_userdata = userdata; } + +/* Set callback function for peer name list changes. + * + * It gets called every time the name list changes(new peer/name, deleted peer) + * Function(Tox *tox, int groupnumber, void *userdata) + */ +void m_callback_group_namelistchange(Messenger *m, void (*function)(Messenger *m, int, int, uint8_t, void *), void *userdata) +{ + m->group_namelistchange = function; + m->group_namelistchange_userdata = userdata; +} + +static int get_chat_num(Messenger *m, Group_Chat *chat) +{ + uint32_t i; + for (i = 0; i < m->numchats; ++i) { //TODO: remove this + if (m->chats[i] == chat) + return i; + } + return -1; +} + static void group_message_function(Group_Chat *chat, int peer_number, uint8_t *message, uint16_t length, void *userdata) { Messenger *m = userdata; - uint32_t i; - - for (i = 0; i < m->numchats; ++i) { //TODO: remove this - if (m->chats[i] == chat) - break; - } + int i = get_chat_num(m, chat); + if (i == -1) + return; if (m->group_message) (*m->group_message)(m, i, peer_number, message, length, m->group_message_userdata); } +static void group_namelistchange_function(Group_Chat *chat, int peer, uint8_t change, void *userdata) +{ + Messenger *m = userdata; + int i = get_chat_num(m, chat); + if (i == -1) + return; + + if (m->group_namelistchange) + (*m->group_namelistchange)(m, i, peer, change, m->group_namelistchange_userdata); +} + + /* Creates a new groupchat and puts it in the chats array. * * return group number on success. @@ -786,6 +841,9 @@ int add_groupchat(Messenger *m) return -1; callback_groupmessage(newchat, &group_message_function, m); + callback_namelistchange(newchat, &group_namelistchange_function, m); + /* TODO: remove this (group nicks should not be tied to the global one) */ + set_nick(newchat, m->name, m->name_length); m->chats[i] = newchat; return i; } @@ -804,6 +862,9 @@ int add_groupchat(Messenger *m) m->chats = temp; callback_groupmessage(temp[m->numchats], &group_message_function, m); + callback_namelistchange(temp[m->numchats], &group_namelistchange_function, m); + /* TODO: remove this (group nicks should not be tied to the global one) */ + set_nick(temp[m->numchats], m->name, m->name_length); ++m->numchats; return (m->numchats - 1); } @@ -953,6 +1014,7 @@ int join_groupchat(Messenger *m, int friendnumber, uint8_t *friend_group_public_ return -1; } + /* send a group message * return 0 on success * return -1 on failure @@ -960,13 +1022,7 @@ int join_groupchat(Messenger *m, int friendnumber, uint8_t *friend_group_public_ int group_message_send(Messenger *m, int groupnumber, uint8_t *message, uint32_t length) { - if ((unsigned int)groupnumber >= m->numchats) - return -1; - - if (m->chats == NULL) - return -1; - - if (m->chats[groupnumber] == NULL) + if (groupnumber_not_valid(m, groupnumber)) return -1; if (group_sendmessage(m->chats[groupnumber], message, length) > 0) @@ -975,6 +1031,33 @@ int group_message_send(Messenger *m, int groupnumber, uint8_t *message, uint32_t return -1; } +/* Return the number of peers in the group chat on success. + * return -1 on failure + */ +int group_number_peers(Messenger *m, int groupnumber) +{ + if (groupnumber_not_valid(m, groupnumber)) + return -1; + + return group_numpeers(m->chats[groupnumber]); +} + +/* List all the peers in the group chat. + * + * Copies the names of the peers to the name[length][MAX_NICK_BYTES] array. + * + * returns the number of peers on success. + * + * return -1 on failure. + */ +int group_names(Messenger *m, int groupnumber, uint8_t names[][MAX_NICK_BYTES], uint16_t length) +{ + if (groupnumber_not_valid(m, groupnumber)) + return -1; + + return group_client_names(m->chats[groupnumber], names, length); +} + static int handle_group(void *object, IP_Port source, uint8_t *packet, uint32_t length) { Messenger *m = object; @@ -1078,7 +1161,7 @@ int file_sendrequest(Messenger *m, int friendnumber, uint8_t filenumber, uint64_ int new_filesender(Messenger *m, int friendnumber, uint64_t filesize, uint8_t *filename, uint16_t filename_length) { if (friend_not_valid(m, friendnumber)) - return 0; + return -1; uint32_t i; @@ -1102,28 +1185,28 @@ int new_filesender(Messenger *m, int friendnumber, uint64_t filesize, uint8_t *f /* Send a file control request. * send_receive is 0 if we want the control packet to target a sending file, 1 if it targets a receiving file. * - * return 1 on success - * return 0 on failure + * return 0 on success + * return -1 on failure */ int file_control(Messenger *m, int friendnumber, uint8_t send_receive, uint8_t filenumber, uint8_t message_id, uint8_t *data, uint16_t length) { if (length > MAX_DATA_SIZE - 3) - return 0; + return -1; if (friend_not_valid(m, friendnumber)) - return 0; + return -1; if (send_receive == 1) { if (m->friendlist[friendnumber].file_receiving[filenumber].status == FILESTATUS_NONE) - return 0; + return -1; } else { if (m->friendlist[friendnumber].file_sending[filenumber].status == FILESTATUS_NONE) - return 0; + return -1; } if (send_receive > 1) - return 0; + return -1; uint8_t packet[MAX_DATA_SIZE]; packet[0] = send_receive; @@ -1133,7 +1216,7 @@ int file_control(Messenger *m, int friendnumber, uint8_t send_receive, uint8_t f if (message_id == FILECONTROL_RESUME_BROKEN) { if (length != sizeof(uint64_t)) - return 0; + return -1; uint8_t remaining[sizeof(uint64_t)]; memcpy(remaining, data, sizeof(uint64_t)); @@ -1181,32 +1264,32 @@ int file_control(Messenger *m, int friendnumber, uint8_t send_receive, uint8_t f break; } - return 1; - } else { return 0; + } else { + return -1; } } #define MIN_SLOTS_FREE 4 /* Send file data. * - * return 1 on success - * return 0 on failure + * return 0 on success + * return -1 on failure */ int file_data(Messenger *m, int friendnumber, uint8_t filenumber, uint8_t *data, uint16_t length) { if (length > MAX_DATA_SIZE - 1) - return 0; + return -1; if (friend_not_valid(m, friendnumber)) - return 0; + return -1; if (m->friendlist[friendnumber].file_sending[filenumber].status != FILESTATUS_TRANSFERRING) - return 0; + return -1; /* Prevent file sending from filling up the entire buffer preventing messages from being sent. */ if (crypto_num_free_sendqueue_slots(m->net_crypto, m->friendlist[friendnumber].crypt_connection_id) < MIN_SLOTS_FREE) - return 0; + return -1; uint8_t packet[MAX_DATA_SIZE]; packet[0] = filenumber; @@ -1214,10 +1297,10 @@ int file_data(Messenger *m, int friendnumber, uint8_t filenumber, uint8_t *data, if (write_cryptpacket_id(m, friendnumber, PACKET_ID_FILE_DATA, packet, length + 1)) { m->friendlist[friendnumber].file_sending[filenumber].transferred += length; - return 1; + return 0; } - return 0; + return -1; } @@ -1371,6 +1454,17 @@ int m_msi_packet(Messenger *m, int friendnumber, uint8_t *data, uint16_t length) return write_cryptpacket_id(m, friendnumber, PACKET_ID_MSI, data, length); } +/* Function to filter out some friend requests*/ +static int friend_already_added(uint8_t *client_id, void *data) +{ + Messenger *m = data; + + if (getfriend_id(m, client_id) == -1) + return 0; + + return -1; +} + /* Send a LAN discovery packet every LAN_DISCOVERY_INTERVAL seconds. */ static void LANdiscovery(Messenger *m) { @@ -1420,6 +1514,8 @@ Messenger *new_messenger(uint8_t ipv6enabled) friendreq_init(&(m->fr), m->net_crypto); LANdiscovery_init(m->dht); set_nospam(&(m->fr), random_int()); + set_filter_function(&(m->fr), &friend_already_added, m); + networking_registerhandler(m->net, NET_PACKET_GROUP_CHATS, &handle_group, m); return m; @@ -1431,6 +1527,10 @@ void kill_messenger(Messenger *m) /* FIXME TODO: ideally cleanupMessenger will mirror initMessenger. * This requires the other modules to expose cleanup functions. */ + uint32_t i, numchats = m->numchats; + for (i = 0; i < numchats; ++i) + del_groupchat(m, i); + kill_DHT(m->dht); kill_net_crypto(m->net_crypto); kill_networking(m->net); @@ -1438,6 +1538,24 @@ void kill_messenger(Messenger *m) free(m); } +/* Check for and handle a timed-out friend request. If the request has + * timed-out then the friend status is set back to FRIEND_ADDED. + * i: friendlist index of the timed-out friend + * t: time + */ +static void check_friend_request_timed_out(Messenger *m, uint32_t i, uint64_t t) +{ + Friend *f = &m->friendlist[i]; + + if (f->friendrequest_lastsent + f->friendrequest_timeout < t) { + set_friend_status(m, i, FRIEND_ADDED); + /* Double the default timeout everytime if friendrequest is assumed + * to have been sent unsuccessfully. + */ + f->friendrequest_timeout *= 2; + } +} + /* TODO: Make this function not suck. */ void do_friends(Messenger *m) { @@ -1465,13 +1583,7 @@ void do_friends(Messenger *m) /* If we didn't connect to friend after successfully sending him a friend request the request is deemed * unsuccessful so we set the status back to FRIEND_ADDED and try again. */ - if (m->friendlist[i].friendrequest_lastsent + m->friendlist[i].friendrequest_timeout < temp_time) { - set_friend_status(m, i, FRIEND_ADDED); - /* Double the default timeout everytime if friendrequest is assumed to have been - * sent unsuccessfully. - */ - m->friendlist[i].friendrequest_timeout *= 2; - } + check_friend_request_timed_out(m, i, temp_time); } IP_Port friendip; @@ -1539,12 +1651,15 @@ void do_friends(Messenger *m) if (data_length >= MAX_NAME_LENGTH || data_length == 0) break; + /* Make sure the NULL terminator is present. */ + data[data_length - 1] = 0; + + /* inform of namechange before we overwrite the old name */ + if (m->friend_namechange) + m->friend_namechange(m, i, data, data_length, m->friend_namechange_userdata); + memcpy(m->friendlist[i].name, data, data_length); m->friendlist[i].name_length = data_length; - m->friendlist[i].name[data_length - 1] = 0; /* Make sure the NULL terminator is present. */ - - if (m->friend_namechange) - m->friend_namechange(m, i, m->friendlist[i].name, data_length, m->friend_namechange_userdata); break; } @@ -1658,7 +1773,7 @@ void do_friends(Messenger *m) break; group_newpeer(m->chats[groupnum], data + crypto_box_PUBLICKEYBYTES); - + chat_bootstrap(m->chats[groupnum], get_friend_ipport(m, i), data + crypto_box_PUBLICKEYBYTES); break; } @@ -1807,6 +1922,18 @@ void do_messenger(Messenger *m) #ifdef LOGGING if (unix_time() > lastdump + DUMPING_CLIENTS_FRIENDS_EVERY_N_SECONDS) { + loglog(" = = = = = = = = \n"); + Assoc_status(m->dht->assoc); + + if (m->numchats > 0) { + size_t c; + + for (c = 0; c < m->numchats; c++) { + loglog("---------------- \n"); + Assoc_status(m->chats[c]->assoc); + } + } + loglog(" = = = = = = = = \n"); lastdump = unix_time(); @@ -1833,7 +1960,30 @@ void do_messenger(Messenger *m) loglog(" = = = = = = = = \n"); - uint32_t num_friends = MIN(m->numfriends, m->dht->num_friends); + uint32_t friend, dhtfriend; + + /* dht contains additional "friends" (requests) */ + uint32_t num_dhtfriends = m->dht->num_friends; + int32_t m2dht[num_dhtfriends]; + int32_t dht2m[num_dhtfriends]; + + for (friend = 0; friend < num_dhtfriends; friend++) { + m2dht[friend] = -1; + dht2m[friend] = -1; + + if (friend >= m->numfriends) + continue; + + for (dhtfriend = 0; dhtfriend < m->dht->num_friends; dhtfriend++) + if (id_equal(m->friendlist[friend].client_id, m->dht->friends_list[dhtfriend].client_id)) { + m2dht[friend] = dhtfriend; + break; + } + } + + for (friend = 0; friend < num_dhtfriends; friend++) + if (m2dht[friend] >= 0) + dht2m[m2dht[friend]] = friend; if (m->numfriends != m->dht->num_friends) { sprintf(logbuffer, "Friend num in DHT %u != friend num in msger %u\n", @@ -1841,34 +1991,34 @@ void do_messenger(Messenger *m) loglog(logbuffer); } - uint32_t friend, ping_lastrecv; + uint32_t ping_lastrecv; + Friend *msgfptr; + DHT_Friend *dhtfptr; - for (friend = 0; friend < num_friends; friend++) { - Friend *msgfptr = &m->friendlist[friend]; - DHT_Friend *dhtfptr = &m->dht->friends_list[friend]; + for (friend = 0; friend < num_dhtfriends; friend++) { + if (dht2m[friend] >= 0) + msgfptr = &m->friendlist[dht2m[friend]]; + else + msgfptr = NULL; - if (memcmp(msgfptr->client_id, dhtfptr->client_id, CLIENT_ID_SIZE)) { - if (sizeof(logbuffer) > 2 * CLIENT_ID_SIZE + 64) { - sprintf(logbuffer, "F[%2u] ID(m) %s != ID(d) ", friend, - ID2String(msgfptr->client_id)); - strcat(logbuffer + strlen(logbuffer), ID2String(dhtfptr->client_id)); - strcat(logbuffer + strlen(logbuffer), "\n"); - } else - sprintf(logbuffer, "F[%2u] ID(m) != ID(d) ", friend); + dhtfptr = &m->dht->friends_list[friend]; + if (msgfptr) { + ping_lastrecv = lastdump - msgfptr->ping_lastrecv; + + if (ping_lastrecv > 999) + ping_lastrecv = 999; + + snprintf(logbuffer, sizeof(logbuffer), "F[%2u:%2u] <%s> %02i [%03u] %s\n", + dht2m[friend], friend, msgfptr->name, msgfptr->crypt_connection_id, + ping_lastrecv, ID2String(msgfptr->client_id)); + loglog(logbuffer); + } else { + snprintf(logbuffer, sizeof(logbuffer), "F[--:%2u] %s\n", + friend, ID2String(dhtfptr->client_id)); loglog(logbuffer); } - ping_lastrecv = lastdump - msgfptr->ping_lastrecv; - - if (ping_lastrecv > 999) - ping_lastrecv = 999; - - snprintf(logbuffer, sizeof(logbuffer), "F[%2u] <%s> %02u [%03u] %s\n", - friend, msgfptr->name, msgfptr->crypt_connection_id, - ping_lastrecv, ID2String(msgfptr->client_id)); - loglog(logbuffer); - for (client = 0; client < MAX_FRIEND_CLIENTS; client++) { Client_data *cptr = &dhtfptr->client_list[client]; IPPTsPng *assoc = NULL; @@ -1995,6 +2145,9 @@ static int messenger_load_state_callback(void *outer, uint8_t *data, uint32_t le if (length == crypto_box_PUBLICKEYBYTES + crypto_box_SECRETKEYBYTES + sizeof(uint32_t)) { set_nospam(&(m->fr), *(uint32_t *)data); load_keys(m->net_crypto, &data[sizeof(uint32_t)]); + + if (m->dht->assoc) + Assoc_self_client_id_changed(m->dht->assoc, m->net_crypto->self_public_key); } else return -1; /* critical */ @@ -2100,12 +2253,12 @@ uint32_t copy_friendlist(Messenger *m, int *out_list, uint32_t list_size) uint32_t ret = 0; for (i = 0; i < m->numfriends; i++) { - if (i >= list_size) { + if (ret >= list_size) { break; /* Abandon ship */ } if (m->friendlist[i].status > 0) { - out_list[i] = i; + out_list[ret] = i; ret++; } } @@ -2146,3 +2299,51 @@ int get_friendlist(Messenger *m, int **out_list, uint32_t *out_list_length) return 0; } +/* Return the number of chats in the instance m. + * You should use this to determine how much memory to allocate + * for copy_chatlist. */ +uint32_t count_chatlist(Messenger *m) +{ + uint32_t ret = 0; + uint32_t i; + + for (i = 0; i < m->numchats; i++) { + if (m->chats[i]) { + ret++; + } + } + + return ret; +} + +/* Copy a list of valid chat IDs into the array out_list. + * If out_list is NULL, returns 0. + * Otherwise, returns the number of elements copied. + * If the array was too small, the contents + * of out_list will be truncated to list_size. */ +uint32_t copy_chatlist(Messenger *m, int *out_list, uint32_t list_size) +{ + if (!out_list) + return 0; + + if (m->numchats == 0) { + return 0; + } + + uint32_t i; + uint32_t ret = 0; + + for (i = 0; i < m->numchats; i++) { + if (ret >= list_size) { + break; /* Abandon ship */ + } + + if (m->chats[i]) { + out_list[ret] = i; + ret++; + } + } + + return ret; +} + diff --git a/toxcore/Messenger.h b/toxcore/Messenger.h index 10ac0eae..6fc23db4 100644 --- a/toxcore/Messenger.h +++ b/toxcore/Messenger.h @@ -198,6 +198,8 @@ typedef struct Messenger { void *group_invite_userdata; void (*group_message)(struct Messenger *m, int, int, uint8_t *, uint16_t, void *); void *group_message_userdata; + void (*group_namelistchange)(struct Messenger *m, int, int, uint8_t, void *); + void *group_namelistchange_userdata; void (*file_sendrequest)(struct Messenger *m, int, uint8_t, uint64_t, uint8_t *, uint16_t, void *); void *file_sendrequest_userdata; @@ -455,6 +457,13 @@ void m_callback_group_invite(Messenger *m, void (*function)(Messenger *m, int, u void m_callback_group_message(Messenger *m, void (*function)(Messenger *m, int, int, uint8_t *, uint16_t, void *), void *userdata); +/* Set callback function for peer name list changes. + * + * It gets called every time the name list changes(new peer/name, deleted peer) + * Function(Tox *tox, int groupnumber, void *userdata) + */ +void m_callback_group_namelistchange(Messenger *m, void (*function)(Messenger *m, int, int, uint8_t, void *), void *userdata); + /* Creates a new groupchat and puts it in the chats array. * * return group number on success. @@ -497,6 +506,21 @@ int join_groupchat(Messenger *m, int friendnumber, uint8_t *friend_group_public_ int group_message_send(Messenger *m, int groupnumber, uint8_t *message, uint32_t length); +/* Return the number of peers in the group chat on success. + * return -1 on failure + */ +int group_number_peers(Messenger *m, int groupnumber); + +/* List all the peers in the group chat. + * + * Copies the names of the peers to the name[length][MAX_NICK_BYTES] array. + * + * returns the number of peers on success. + * + * return -1 on failure. + */ +int group_names(Messenger *m, int groupnumber, uint8_t names[][MAX_NICK_BYTES], uint16_t length); + /****************FILE SENDING*****************/ @@ -633,5 +657,17 @@ uint32_t copy_friendlist(Messenger *m, int *out_list, uint32_t list_size); */ int get_friendlist(Messenger *m, int **out_list, uint32_t *out_list_length); +/* Return the number of chats in the instance m. + * You should use this to determine how much memory to allocate + * for copy_chatlist. */ +uint32_t count_chatlist(Messenger *m); + +/* Copy a list of valid chat IDs into the array out_list. + * If out_list is NULL, returns 0. + * Otherwise, returns the number of elements copied. + * If the array was too small, the contents + * of out_list will be truncated to list_size. */ +uint32_t copy_chatlist(Messenger *m, int *out_list, uint32_t list_size); + #endif diff --git a/toxcore/assoc.c b/toxcore/assoc.c new file mode 100644 index 00000000..4dc91671 --- /dev/null +++ b/toxcore/assoc.c @@ -0,0 +1,926 @@ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "DHT.h" +#include "assoc.h" +#include "ping.h" + +#include "LAN_discovery.h" + +#include +#include "util.h" + +/* + * BASIC OVERVIEW: + * + * Hash: The client_id is hashed with a local hash function. + * Hashes are used in multiple places for searching. + * Bucket: The first n bits of the client_id are used to + * select a bucket. This speeds up sorting, but the more + * important reason is to enforce a spread in the space of + * client_ids available. + * + * + * Candidates: + * + * Candidates are kept in buckets of hash tables. The hash + * function is calculated from the client_id. Up to + * HASH_COLLIDE_COUNT alternative positions are tried if + * the inital position is already used by a different entry. + * The collision function is multiplicative, not additive. + * + * A new candidate can bump an existing candidate, if it is + * more "desirable": Seen beats Heard. + */ + +/* candidates: alternative places for the same hash value */ +#define HASH_COLLIDE_COUNT 5 + +/* bucket size shall be co-prime to this */ +#define HASH_COLLIDE_PRIME 101 + +/* candidates: bump entries: timeout values for seen/heard to be considered of value */ +#define CANDIDATES_SEEN_TIMEOUT 1800 +#define CANDIDATES_HEARD_TIMEOUT 600 + +/* distance/index: index size & access mask */ +#define DISTANCE_INDEX_INDEX_BITS (64 - DISTANCE_INDEX_DISTANCE_BITS) +#define DISTANCE_INDEX_INDEX_MASK ((1 << DISTANCE_INDEX_INDEX_BITS) - 1) + +/* types to stay consistent */ +typedef uint16_t bucket_t; +typedef uint32_t hash_t; +typedef uint16_t usecnt_t; + +/* abbreviations ... */ +typedef Assoc_distance_relative_callback dist_rel_cb; +typedef Assoc_distance_absolute_callback dist_abs_cb; + +/* + * Client_data wrapped with additional data + */ +typedef struct Client_entry { + hash_t hash; + + /* shortcuts & rumors: timers and data */ + uint64_t used_at; + uint64_t seen_at; + uint64_t heard_at; + + uint16_t seen_family; + uint16_t heard_family; + + IP_Port assoc_heard4; + IP_Port assoc_heard6; + + Client_data client; +} Client_entry; + +typedef struct candidates_bucket { + Client_entry *list; /* hashed list */ +} candidates_bucket; + +struct Assoc { + hash_t self_hash; /* hash of self_client_id */ + uint8_t self_client_id[CLIENT_ID_SIZE]; /* don't store entries for this */ + + /* association centralization: clients not in use */ + size_t candidates_bucket_bits; + size_t candidates_bucket_count; + size_t candidates_bucket_size; + candidates_bucket *candidates; +}; + +/*****************************************************************************/ +/* HELPER FUNCTIONS */ +/*****************************************************************************/ + +/* the complete distance would be CLIENT_ID_SIZE long... + * returns DISTANCE_INDEX_DISTANCE_BITS valid bits */ +static uint64_t id_distance(Assoc *assoc, void *callback_data, uint8_t *id_ref, uint8_t *id_test) +{ + /* with BIG_ENDIAN, this would be a one-liner... */ + uint64_t retval = 0; + + uint8_t pos = 0, bits = DISTANCE_INDEX_DISTANCE_BITS; + + while (bits > 8) { + uint8_t distance = abs((int8_t)id_ref[pos] ^ (int8_t)id_test[pos]); + retval = (retval << 8) | distance; + bits -= 8; + pos++; + } + + return (retval << bits) | ((id_ref[pos] ^ id_test[pos]) >> (8 - bits)); +} + +/* qsort() callback for a sorting by id_distance() values */ +static int dist_index_comp(const void *a, const void *b) +{ + const uint64_t *_a = a; + const uint64_t *_b = b; + + if (*_a < *_b) + return -1; + + if (*_a > *_b) + return 1; + + return 0; +} + +/* get actual entry to a distance_index */ +static Client_entry *dist_index_entry(Assoc *assoc, uint64_t dist_ind) +{ + if ((dist_ind & DISTANCE_INDEX_INDEX_MASK) == DISTANCE_INDEX_INDEX_MASK) + return NULL; + + size_t total = assoc->candidates_bucket_count * assoc->candidates_bucket_size; + uint32_t index = dist_ind & DISTANCE_INDEX_INDEX_MASK; + + if (index < total) { + bucket_t b_id = index / assoc->candidates_bucket_size; + candidates_bucket *cnd_bckt = &assoc->candidates[b_id]; + size_t b_ix = index % assoc->candidates_bucket_size; + Client_entry *entry = &cnd_bckt->list[b_ix]; + + if (entry->hash) + return entry; + } + + return NULL; +} + +/* get actual entry's client_id to a distance_index */ +static uint8_t *dist_index_id(Assoc *assoc, uint64_t dist_ind) +{ + Client_entry *entry = dist_index_entry(assoc, dist_ind); + + if (entry) + return entry->client.client_id; + + return NULL; +} + +/* sorts first .. last, i.e. last is included */ +static void dist_index_bubble(Assoc *assoc, uint64_t *dist_list, size_t first, size_t last, uint8_t *id, + void *custom_data, Assoc_distance_relative_callback dist_rel_func) +{ + size_t i, k; + + for (i = first; i <= last; i++) { + uint8_t *id1 = dist_index_id(assoc, dist_list[i]); + + for (k = i + 1; k <= last; k++) { + uint8_t *id2 = dist_index_id(assoc, dist_list[k]); + + if (id1 && id2) + if (dist_rel_func(assoc, custom_data, id, id1, id2) == 2) { + uint64_t swap = dist_list[i]; + dist_list[i] = dist_list[k]; + dist_list[k] = swap; + } + } + } +} + +/* TODO: Check that there isn't a function like this elsewhere hidden. + * E.g. the one which creates a handshake_id isn't usable for this, it must + * always map the same ID to the same hash. + * + * Result is NOT MAPPED to CANDIDATES_TO_KEEP range, i.e. map before using + * it for list access. */ +static hash_t id_hash(Assoc *assoc, uint8_t *id) +{ + uint32_t i, res = 0x19a64e82; + + for (i = 0; i < CLIENT_ID_SIZE; i++) + res = ((res << 1) ^ id[i]) + (res >> 31); + + /* can't have zero as hash, a) marks an unused spot, + * b) collision function is multiplicative */ + if (!(res % assoc->candidates_bucket_size)) + res++; + + return res; +} + +/* up to HASH_COLLIDE_COUNT calls to different spots, + * result IS mapped to CANDIDATES_TO_KEEP range */ +static hash_t hash_collide(Assoc *assoc, hash_t hash) +{ + uint64_t hash64 = hash % assoc->candidates_bucket_size; + hash64 = (hash64 * HASH_COLLIDE_PRIME) % assoc->candidates_bucket_size; + + hash_t retval = hash64; + + /* this should never happen when CANDIDATES_TO_KEEP is prime and hash not a multiple + * (id_hash() checks for a multiple and returns a different hash in that case) + * + * ( 1 .. (prime - 1) is a group over multiplication and every number has its inverse + * in the group, so no multiplication should ever end on zero as long neither + * of the two factors was zero-equivalent ) + * + * BUT: because the usage of the word "never" invokes Murphy's law, catch it */ + if (!retval) { +#ifdef DEBUG + fprintf(stderr, "assoc::hash_collide: hash %u, bucket size %u => %u!", hash, (uint)assoc->candidates_bucket_size, + retval); + assert(retval != 0); +#endif + retval = 1; + } + + return retval; +} + +/* returns the "seen" assoc related to the ipp */ +static IPPTsPng *entry_assoc(Client_entry *cl_entry, IP_Port *ipp) +{ + if (!cl_entry) + return NULL; + + if (ipp->ip.family == AF_INET) + return &cl_entry->client.assoc4; + + if (ipp->ip.family == AF_INET6) + return &cl_entry->client.assoc6; + + return NULL; +} + +/* returns the "heard" assoc related to the ipp */ +static IP_Port *entry_heard_get(Client_entry *entry, IP_Port *ipp) +{ + if (ipp->ip.family == AF_INET) + return &entry->assoc_heard4; + else if (ipp->ip.family == AF_INET6) + return &entry->assoc_heard6; + else + return NULL; +} + +/* store a "heard" entry + * overwrites empty entry, does NOT overwrite non-LAN ip with + * LAN ip + * + * returns 1 if the entry did change */ +static int entry_heard_store(Client_entry *entry, IPPTs *ippts) +{ + if (!entry || !ippts) + return 0; + + if (!ipport_isset(&ippts->ip_port)) + return 0; + + IP_Port *heard, *ipp = &ippts->ip_port; + + if (ipp->ip.family == AF_INET) + heard = &entry->assoc_heard4; + else if (ipp->ip.family == AF_INET6) + heard = &entry->assoc_heard6; + else + return 0; + + if (ipport_equal(ipp, heard)) + return 0; + + if (!ipport_isset(heard)) { + *heard = *ipp; + entry->heard_at = ippts->timestamp; + entry->heard_family = ipp->ip.family; + return 1; + } + + /* don't destroy a good address with a crappy one + * (unless we're very timed out) */ + uint8_t LAN_ipp = LAN_ip(ipp->ip) == 0; + uint8_t LAN_entry = LAN_ip(heard->ip) == 0; + + if (LAN_ipp && !LAN_entry && !is_timeout(entry->heard_at, CANDIDATES_HEARD_TIMEOUT)) + return 0; + + *heard = *ipp; + entry->heard_at = ippts->timestamp; + entry->heard_family = ipp->ip.family; + + return 1; +} + +/* maps Assoc callback signature to id_closest() */ +static int assoc_id_closest(Assoc *assoc, void *callback_data, uint8_t *client_id, uint8_t *client_id1, + uint8_t *client_id2) +{ + return id_closest(client_id, client_id1, client_id2); +} + +static bucket_t id_bucket(uint8_t *id, uint8_t bits) +{ + /* return the first "bits" bits of id */ + bucket_t retval = 0; + + uint8_t pos = 0; + + while (bits > 8) { + retval = (retval << 8) | id[pos++]; + bits -= 8; + } + + return (retval << bits) | (id[pos] >> (8 - bits)); +} + +/*****************************************************************************/ +/* CANDIDATES FUNCTIONS */ +/*****************************************************************************/ + + +static bucket_t candidates_id_bucket(Assoc *assoc, uint8_t *id) +{ + return id_bucket(id, assoc->candidates_bucket_bits); +} + +static uint8_t candidates_search(Assoc *assoc, uint8_t *id, hash_t hash, Client_entry **entryptr) +{ + bucket_t bucket = candidates_id_bucket(assoc, id); + candidates_bucket *cnd_bckt = &assoc->candidates[bucket]; + size_t coll, pos = hash % assoc->candidates_bucket_size; + + for (coll = 0; coll < HASH_COLLIDE_COUNT; pos = hash_collide(assoc, pos) , coll++) { + Client_entry *entry = &cnd_bckt->list[pos]; + + if (entry->hash == hash) + if (id_equal(entry->client.client_id, id)) { + *entryptr = entry; + return 1; + } + } + + *entryptr = NULL; + return 0; +} + +static void candidates_update_assoc(Assoc *assoc, Client_entry *entry, uint8_t used, IPPTs *ippts_send, + IP_Port *ipp_recv) +{ + if (!assoc || !entry || !ippts_send) + return; + + IPPTsPng *ipptsp = entry_assoc(entry, &ippts_send->ip_port); + + if (!ipptsp) + return; + + if (used) + entry->used_at = unix_time(); + + /* do NOT do anything related to wanted, that's handled outside, + * just update the assoc (in the most sensible way) + */ + if (ipp_recv) { + ipptsp->ip_port = ippts_send->ip_port; + ipptsp->timestamp = ippts_send->timestamp; + ipptsp->ret_ip_port = *ipp_recv; + ipptsp->ret_timestamp = unix_time(); + + entry->seen_at = unix_time(); + entry->seen_family = ippts_send->ip_port.ip.family; + + return; + } + + entry_heard_store(entry, ippts_send); +} + +static uint8_t candidates_create_internal(Assoc *assoc, hash_t hash, uint8_t *id, uint8_t seen, + uint8_t used, bucket_t *bucketptr, size_t *posptr) +{ + if (!assoc || !id || !bucketptr || !posptr) + return 0; + + bucket_t bucket = candidates_id_bucket(assoc, id); + candidates_bucket *cnd_bckt = &assoc->candidates[bucket]; + + size_t coll, pos = hash % assoc->candidates_bucket_size, check; + size_t pos_check[6]; + + memset(pos_check, 0, sizeof(pos_check)); + + for (coll = 0; coll < HASH_COLLIDE_COUNT; pos = hash_collide(assoc, pos) , coll++) { + Client_entry *entry = &cnd_bckt->list[pos]; + + /* unset */ + if (!entry->hash) { + *bucketptr = bucket; + *posptr = pos; + + return 1; + } + + /* 0. bad + * 1. seen bad, heard good + * 2. seen good + * 3. used */ + if (!is_timeout(entry->used_at, BAD_NODE_TIMEOUT)) + check = 3; + + if (!is_timeout(entry->seen_at, CANDIDATES_SEEN_TIMEOUT)) + check = 2; + else if (!is_timeout(entry->heard_at, CANDIDATES_HEARD_TIMEOUT)) + check = 1; + else + check = 0; + + if (!pos_check[check]) + pos_check[check] = pos + 1; + } + + /* used > seen > heard > bad */ + size_t i, pos_max = used ? 3 : (seen ? 2 : 1); + + for (i = 0; i < pos_max; i++) + if (pos_check[i]) { + *bucketptr = bucket; + *posptr = pos_check[i] - 1; + + return 1; + } + + return 0; +} + +static uint8_t candidates_create_new(Assoc *assoc, hash_t hash, uint8_t *id, uint8_t used, + IPPTs *ippts_send, IP_Port *ipp_recv) +{ + if (!assoc || !id || !ippts_send) + return 0; + + bucket_t bucket; + size_t pos; + + if (!candidates_create_internal(assoc, hash, id, ipp_recv != NULL, used, &bucket, &pos)) + return 0; + + candidates_bucket *cnd_bckt = &assoc->candidates[bucket]; + Client_entry *entry = &cnd_bckt->list[pos]; + memset(entry, 0, sizeof(*entry)); + IPPTsPng *ipptsp = entry_assoc(entry, &ippts_send->ip_port); + + if (!ipptsp) + return 0; + + entry->hash = hash; + id_copy(entry->client.client_id, id); + + if (used) + entry->used_at = unix_time(); + + if (ipp_recv && !ipport_isset(ipp_recv)) + ipp_recv = NULL; + + if (ipp_recv) { + entry->seen_at = ippts_send->timestamp; + entry->seen_family = ippts_send->ip_port.ip.family; + + ipptsp->ip_port = ippts_send->ip_port; + ipptsp->timestamp = ippts_send->timestamp; + ipptsp->ret_ip_port = *ipp_recv; + ipptsp->ret_timestamp = unix_time(); + } else { + IP_Port *heard = entry_heard_get(entry, &ippts_send->ip_port); + + if (heard) { + entry->heard_at = ippts_send->timestamp; + entry->heard_family = ippts_send->ip_port.ip.family; + + *heard = ippts_send->ip_port; + } + } + + return 1; +} + +/*****************************************************************************/ + +static void client_id_self_update(Assoc *assoc) +{ + if (assoc->self_hash || !assoc->self_client_id) + return; + + if (!assoc->self_hash) { + size_t i, sum = 0; + + for (i = 0; i < crypto_box_PUBLICKEYBYTES; i++) + sum |= assoc->self_client_id[i]; + + if (!sum) + return; + + assoc->self_hash = id_hash(assoc, assoc->self_client_id); + } + +#ifdef LOGGING + loglog("assoc: id is now set, purging cache of self-references...\n"); +#endif + + /* if we already added some (or loaded some) entries, + * look and remove if we find a match + */ + bucket_t b_id = candidates_id_bucket(assoc, assoc->self_client_id); + candidates_bucket *cnd_bckt = &assoc->candidates[b_id]; + size_t i, pos = assoc->self_hash % assoc->candidates_bucket_size; + + for (i = 0; i < HASH_COLLIDE_COUNT; pos = hash_collide(assoc, pos), i++) { + Client_entry *entry = &cnd_bckt->list[pos]; + + if (entry->hash == assoc->self_hash) + if (id_equal(entry->client.client_id, assoc->self_client_id)) + entry->hash = 0; + } +} + +/*****************************************************************************/ +/* TRIGGER FUNCTIONS */ +/*****************************************************************************/ + +/* Central entry point for new associations: add a new candidate to the cache + * seen should be 0 (zero), if the candidate was announced by someone else, + * seen should be 1 (one), if there is confirmed connectivity (a definite response) + */ +uint8_t Assoc_add_entry(Assoc *assoc, uint8_t *id, IPPTs *ippts_send, IP_Port *ipp_recv, uint8_t used) +{ + if (!assoc || !id || !ippts_send) + return 0; + + if (!assoc->self_hash) { + client_id_self_update(assoc); + + if (!assoc->self_hash) + return 0; + } + + if (!ipport_isset(&ippts_send->ip_port)) + return 0; + + if (ipp_recv && !ipport_isset(ipp_recv)) + ipp_recv = NULL; + + hash_t hash = id_hash(assoc, id); + + if (hash == assoc->self_hash) + if (id_equal(id, assoc->self_client_id)) + return 0; + + /* if it's new: + * callback, if there's desire, add to clients, else to candidates + * + * if it's "old": + * if it's client: refresh + * if it's candidate: + * if !ipp_recv, refresh + * if ipp_recv: callback, if there's desire, move to candidates + */ + Client_entry *cnd_entry; + + if (!candidates_search(assoc, id, hash, &cnd_entry)) { + if (candidates_create_new(assoc, hash, id, used, ippts_send, ipp_recv)) + return 1; + else + return 0; + } else { + candidates_update_assoc(assoc, cnd_entry, used, ippts_send, ipp_recv); + return 2; + } +} + +/*****************************************************************************/ +/* MAIN USE */ +/*****************************************************************************/ + +uint8_t Assoc_get_close_entries(Assoc *assoc, Assoc_close_entries *state) +{ + if (!assoc || !state || !state->wanted_id || !state->result) + return 0; + + if (!assoc->self_hash) { + client_id_self_update(assoc); + + if (!assoc->self_hash) + return 0; + } + + if (!state->distance_relative_func) + state->distance_relative_func = assoc_id_closest; + + if (!state->distance_absolute_func) + state->distance_absolute_func = id_distance; + + size_t dist_list_len = assoc->candidates_bucket_count * assoc->candidates_bucket_size; + uint64_t dist_list[dist_list_len]; + memset(dist_list, ~0, dist_list_len * sizeof(dist_list[0])); + bucket_t b; + size_t i; + + for (b = 0; b < assoc->candidates_bucket_count; b++) { + candidates_bucket *cnd_bckt = &assoc->candidates[b]; + + for (i = 0; i < assoc->candidates_bucket_size; i++) { + Client_entry *entry = &cnd_bckt->list[i]; + + if (entry->hash) { + if (state->flags & ProtoIPv4) { + if (!ipport_isset(&entry->client.assoc4.ip_port)) + continue; + + if (!(state->flags & LANOk)) + if (!LAN_ip(entry->client.assoc4.ip_port.ip)) + continue; + } + + if (state->flags & ProtoIPv6) { + if (!ipport_isset(&entry->client.assoc6.ip_port)) + continue; + + if (!(state->flags & LANOk)) + if (!LAN_ip(entry->client.assoc6.ip_port.ip)) + continue; + } + + uint64_t dist = state->distance_absolute_func(assoc, state->custom_data, state->wanted_id, entry->client.client_id); + uint32_t index = b * assoc->candidates_bucket_size + i; + dist_list[index] = (dist << DISTANCE_INDEX_INDEX_BITS) | index; + } + } + } + + qsort(dist_list, dist_list_len, sizeof(dist_list[0]), dist_index_comp); + + /* ok, ok, it's not *perfectly* sorted, because we used an absolute distance + * go over the result and see if we need to "smoothen things out" + * because those should be only very few and short streaks, the worst regularly + * used sorting function aka bubble sort is used */ + uint64_t dist_prev = ~0; + size_t ind_prev = ~0, ind_curr; + size_t len = 1; + + for (ind_curr = 0; ind_curr < dist_list_len; ind_curr++) { + /* sorted increasingly, so an invalid entry marks the end */ + if ((dist_list[ind_curr] & DISTANCE_INDEX_INDEX_MASK) == DISTANCE_INDEX_INDEX_MASK) + break; + + uint64_t dist_curr = dist_list[ind_curr] >> DISTANCE_INDEX_INDEX_BITS; + + if (dist_prev == dist_curr) + len++; + else { + if (len > 1) + dist_index_bubble(assoc, dist_list, ind_prev, ind_curr - 1, state->wanted_id, state->custom_data, + state->distance_relative_func); + + dist_prev = dist_curr; + ind_prev = ind_curr; + len = 1; + } + } + + if (len > 1) + dist_index_bubble(assoc, dist_list, ind_prev, ind_curr - 1, state->wanted_id, state->custom_data, + state->distance_relative_func); + + /* ok, now dist_list is a strictly ascending sorted list of nodes + * a) extract CLOSE_QUOTA_USED clients, not timed out + * b) extract (1 - QUOTA) (better!) clients & candidates, not timed out + * c) save candidates which would be better, if contact can be established */ + size_t client_quota_good = 0, pos = 0; + size_t client_quota_max = state->count_good; + + ssize_t taken_last = - 1; + + for (i = 0; (i < dist_list_len) && (pos < state->count); i++) { + Client_entry *entry = dist_index_entry(assoc, dist_list[i]); + + if (entry && entry->hash) { + if (client_quota_good >= client_quota_max) { + state->result[pos++] = &entry->client; + taken_last = i; + } else { + if (state->flags & (ProtoIPv4 | ProtoIPv6)) { + if ((state->flags & ProtoIPv4) && is_timeout(entry->client.assoc4.timestamp, BAD_NODE_TIMEOUT)) + continue; + + if ((state->flags & ProtoIPv6) && is_timeout(entry->client.assoc6.timestamp, BAD_NODE_TIMEOUT)) + continue; + } else if (is_timeout(entry->seen_at, BAD_NODE_TIMEOUT)) + continue; + + state->result[pos++] = &entry->client; + client_quota_good++; + taken_last = i; + } + } + } + + /* if we had not enough valid entries the list might still not be filled. + * + * start again from last taken client, but leave out any requirement + */ + if (pos < state->count) { + for (i = taken_last + 1; (i < dist_list_len) && (pos < state->count); i++) { + Client_entry *entry = dist_index_entry(assoc, dist_list[i]); + + if (entry && entry->hash) + state->result[pos++] = &entry->client; + } + } + + return pos; +} + +/*****************************************************************************/ +/* GLOBAL STRUCTURE FUNCTIONS */ +/*****************************************************************************/ + +static uint8_t odd_min9_is_prime(size_t value) +{ + size_t i = 3; + + while (i * i <= value) { + if (!(value % i)) + return 0; + + i += 2; + } + + return 1; +} + +static size_t prime_upto_min9(size_t limit) +{ + /* even => odd */ + limit = limit - (1 - (limit % 2)); + + while (!odd_min9_is_prime(limit)) + limit -= 2; + + return limit; +} + +/* create */ +Assoc *new_Assoc(size_t bits, size_t entries, uint8_t *public_id) +{ + if (!public_id) + return NULL; + + Assoc *assoc = calloc(1, sizeof(*assoc)); + + if (!assoc) + return NULL; + + /* + * bits must be in [ 2 .. 15 ] + * entries must be a prime + */ + if (bits < 2) + bits = 2; + else if (bits > 15) + bits = 15; + + assoc->candidates_bucket_bits = bits; + assoc->candidates_bucket_count = 1U << bits; + + if (entries < 25) { + if (entries <= 6) + entries = 5; + else { + entries = entries - (1 - (entries % 2)); /* even => odd */ + + /* 7..23: all odds but 9&15 are prime */ + if (!(entries % 3)) /* 9, 15 */ + entries -= 2; /* 7, 13 */ + } + } else if (entries > ((1 << 17) - 1)) /* 130k+ */ + entries = (1 << 17) - 1; + else { + /* 9+: test and find a prime less or equal */ + size_t entries_test = prime_upto_min9(entries); + + if (entries_test == HASH_COLLIDE_PRIME) /* disallowed */ + entries_test = prime_upto_min9(entries_test - 1); + + if (entries_test != entries) { +#ifdef LOGGING + sprintf(logbuffer, "new_Assoc(): trimmed %i to %i.\n", (int)entries, (int)entries_test); + loglog(logbuffer); +#endif + entries = (size_t)entries_test; + } + } + + assoc->candidates_bucket_size = entries; + + /* allocation: preferably few blobs */ + size_t bckt, cix; + Client_entry *clients = malloc(sizeof(*clients) * assoc->candidates_bucket_count * assoc->candidates_bucket_size); + candidates_bucket *lists = malloc(sizeof(*lists) * assoc->candidates_bucket_count); + + for (bckt = 0; bckt < assoc->candidates_bucket_count; bckt++) { + candidates_bucket *list = &lists[bckt]; + + list->list = &clients[bckt * assoc->candidates_bucket_size]; + + for (cix = 0; cix < assoc->candidates_bucket_size; cix++) + list->list[cix].hash = 0; + } + + assoc->candidates = lists; + + id_copy(assoc->self_client_id, public_id); + client_id_self_update(assoc); + + return assoc; +} + +Assoc *new_Assoc_default(uint8_t *public_id) +{ + /* original 8, 251 averages to ~32k entries... probably the whole DHT :D + * 320 entries is fine, hopefully */ + return new_Assoc(6, 15, public_id); +} + +/* own client_id, assocs for this have to be ignored */ +void Assoc_self_client_id_changed(Assoc *assoc, uint8_t *id) +{ + if (assoc && id) { + assoc->self_hash = 0; + id_copy(assoc->self_client_id, id); + client_id_self_update(assoc); + } +} + +/* destroy */ +void kill_Assoc(Assoc *assoc) +{ + if (assoc) { + free(assoc->candidates->list); + free(assoc->candidates); + free(assoc); + } +} + +#ifdef LOGGING + +static char buffer[CLIENT_ID_SIZE * 2 + 1]; +static char *idpart2str(uint8_t *id, size_t len) +{ + if (len > CLIENT_ID_SIZE) + len = CLIENT_ID_SIZE; + + size_t i; + + for (i = 0; i < len; i++) + sprintf(buffer + i * 2, "%02hhx", id[i]); + + buffer[len * 2] = 0; + return buffer; +} + +void Assoc_status(Assoc *assoc) +{ + if (!assoc) { + loglog("Assoc status: no assoc\n"); + return; + } + + loglog("[b:p] hash => [id...] used, seen, heard\n"); + + size_t bid, cid, total = 0; + + for (bid = 0; bid < assoc->candidates_bucket_count; bid++) { + candidates_bucket *bucket = &assoc->candidates[bid]; + + for (cid = 0; cid < assoc->candidates_bucket_size; cid++) { + Client_entry *entry = &bucket->list[cid]; + + if (entry->hash) { + sprintf(logbuffer, "[%3i:%3i] %08x => [%s...] %i, %i(%c), %i(%c)\n", + (int)bid, (int)cid, entry->hash, idpart2str(entry->client.client_id, 8), + entry->used_at ? (int)(unix_time() - entry->used_at) : 0, + entry->seen_at ? (int)(unix_time() - entry->seen_at) : 0, + entry->seen_at ? (entry->seen_family == AF_INET ? '4' : (entry->seen_family == AF_INET6 ? '6' : '?')) : '?', + entry->heard_at ? (int)(unix_time() - entry->heard_at) : 0, + entry->heard_at ? (entry->heard_family == AF_INET ? '4' : (entry->heard_family == AF_INET6 ? '6' : '?')) : '?'); + loglog(logbuffer); + total++; + } + } + } + + if (total) { + sprintf(logbuffer, "Total: %i entries, table usage %i%%.\n", (int)total, + (int)(total * 100 / (assoc->candidates_bucket_count * assoc->candidates_bucket_size))); + loglog(logbuffer); + } +} + +#endif diff --git a/toxcore/assoc.h b/toxcore/assoc.h new file mode 100644 index 00000000..d3055ce3 --- /dev/null +++ b/toxcore/assoc.h @@ -0,0 +1,93 @@ + +#ifndef __ASSOC_H__ +#define __ASSOC_H__ + +/* used by rendezvous */ +#define ASSOC_AVAILABLE + +/* For the legalese parts, see tox.h. */ + +/* + * Module to store currently unused ID <=> IP associations + * for a potential future use + */ + +typedef struct Assoc Assoc; + +/*****************************************************************************/ + +/* custom distance handler, if it's not ID-distance based + * return values exactly like id_closest() */ +typedef int (*Assoc_distance_relative_callback)(Assoc *assoc, void *callback_data, uint8_t *client_id, + uint8_t *client_id1, uint8_t *client_id2); + +#define DISTANCE_INDEX_DISTANCE_BITS 44 + +/* absolute distance: can be same for different client_id_check values + * return value should have DISTANCE_INDEX_DISTANCE_BITS valid bits */ +typedef uint64_t (*Assoc_distance_absolute_callback)(Assoc *assoc, void *callback_data, + uint8_t *client_id_ref, uint8_t *client_id_check); + +/*****************************************************************************/ + +/* Central entry point for new associations: add a new candidate to the cache + * returns 1 if entry is stored, 2 if existing entry was updated, 0 else */ +uint8_t Assoc_add_entry(Assoc *assoc, uint8_t *id, IPPTs *ippts_send, IP_Port *ipp_recv, uint8_t used); + +/*****************************************************************************/ + +typedef enum AssocCloseEntriesFlags { + ProtoIPv4 = 1, + ProtoIPv6 = 2, + LANOk = 4, +} AssocCloseEntriesFlags; + +typedef struct Assoc_close_entries { + void *custom_data; /* given to distance functions */ + uint8_t *wanted_id; /* the target client_id */ + uint8_t flags; /* additional flags */ + + Assoc_distance_relative_callback distance_relative_func; + Assoc_distance_absolute_callback distance_absolute_func; + + uint8_t count_good; /* that many should be "good" w.r.t. timeout */ + uint8_t count; /* allocated number of close_indices */ + Client_data **result; +} Assoc_close_entries; + +/* find up to close_count nodes to put into close_nodes_used of ID_Nodes + * the distance functions can be NULL, then standard distance functions will be used + * the caller is responsible for allocating close_indices of sufficient size + * + * returns 0 on error + * returns the number of found nodes and the list of indices usable by Assoc_client() + * the caller is assumed to be registered from Assoc_register_callback() + * if they aren't, they should copy the Client_data and call Assoc_client_drop() + */ +uint8_t Assoc_get_close_entries(Assoc *assoc, Assoc_close_entries *close_entries); + +/*****************************************************************************/ + +/* create: default sizes (6, 5 => 320 entries) */ +Assoc *new_Assoc_default(uint8_t *public_id); + +/* create: customized sizes + * total is (2^bits) * entries + * bits should be between 2 and 15 (else it's trimmed) + * entries will be reduced to the closest prime smaller or equal + * + * preferably bits should be large and entries small to ensure spread + * in the search space (e. g. 5, 5 is preferable to 2, 41) */ +Assoc *new_Assoc(size_t bits, size_t entries, uint8_t *public_id); + +/* public_id changed (loaded), update which entry isn't stored */ +void Assoc_self_client_id_changed(Assoc *assoc, uint8_t *public_id); + +/* destroy */ +void kill_Assoc(Assoc *assoc); + +#ifdef LOGGING +void Assoc_status(Assoc *assoc); +#endif + +#endif /* !__ASSOC_H__ */ diff --git a/toxcore/friend_requests.c b/toxcore/friend_requests.c index 67977c23..589bd315 100644 --- a/toxcore/friend_requests.c +++ b/toxcore/friend_requests.c @@ -96,6 +96,12 @@ void callback_friendrequest(Friend_Requests *fr, void (*function)(uint8_t *, uin fr->handle_friendrequest_isset = 1; fr->handle_friendrequest_userdata = userdata; } +/* Set the function used to check if a friend request should be displayed to the user or not. */ +void set_filter_function(Friend_Requests *fr, int (*function)(uint8_t *, void *), void *userdata) +{ + fr->filter_function = function; + fr->filter_function_userdata = userdata; +} /* Add to list of received friend requests. */ static void addto_receivedlist(Friend_Requests *fr, uint8_t *client_id) @@ -141,6 +147,10 @@ static int friendreq_handlepacket(void *object, IP_Port source, uint8_t *source_ if (memcmp(packet, &fr->nospam, sizeof(fr->nospam)) != 0) return 1; + if (fr->filter_function) + if ((*fr->filter_function)(source_pubkey, fr->filter_function_userdata) != 0) + return 1; + addto_receivedlist(fr, source_pubkey); (*fr->handle_friendrequest)(source_pubkey, packet + 4, length - 4, fr->handle_friendrequest_userdata); return 0; diff --git a/toxcore/friend_requests.h b/toxcore/friend_requests.h index b5c46056..c655669d 100644 --- a/toxcore/friend_requests.h +++ b/toxcore/friend_requests.h @@ -34,6 +34,8 @@ typedef struct { uint8_t handle_friendrequest_isset; void *handle_friendrequest_userdata; + int (*filter_function)(uint8_t *, void *); + void *filter_function_userdata; /* NOTE: The following is just a temporary fix for the multiple friend requests received at the same time problem. * TODO: Make this better (This will most likely tie in with the way we will handle spam.) */ @@ -53,11 +55,17 @@ void set_nospam(Friend_Requests *fr, uint32_t num); uint32_t get_nospam(Friend_Requests *fr); /* Set the function that will be executed when a friend request for us is received. - * Function format is function(uint8_t * public_key, uint8_t * data, uint16_t length) + * Function format is function(uint8_t * public_key, uint8_t * data, uint16_t length, void * userdata) */ void callback_friendrequest(Friend_Requests *fr, void (*function)(uint8_t *, uint8_t *, uint16_t, void *), void *userdata); +/* Set the function used to check if a friend request should be displayed to the user or not. + * Function format is int function(uint8_t * public_key, void * userdata) + * It must return 0 if the request is ok (anything else if it is bad.) + */ +void set_filter_function(Friend_Requests *fr, int (*function)(uint8_t *, void *), void *userdata); + /* Sets up friendreq packet handlers. */ void friendreq_init(Friend_Requests *fr, Net_Crypto *c); diff --git a/toxcore/group_chats.c b/toxcore/group_chats.c index 6ad41c7f..16d8f344 100644 --- a/toxcore/group_chats.c +++ b/toxcore/group_chats.c @@ -27,6 +27,8 @@ #endif #include "group_chats.h" +#include "assoc.h" +#include "LAN_discovery.h" #include "util.h" #define GROUPCHAT_MAXDATA_LENGTH (MAX_DATA_SIZE - (1 + crypto_box_PUBLICKEYBYTES * 2 + crypto_box_NONCEBYTES)) @@ -53,7 +55,6 @@ typedef struct { } sendnodes_data; - /* * check if peer with client_id is in peer array. * @@ -211,43 +212,61 @@ static int addpeer(Group_Chat *chat, uint8_t *client_id) memset(&(temp[chat->numpeers]), 0, sizeof(Group_Peer)); chat->group = temp; + id_copy(chat->group[chat->numpeers].client_id, client_id); chat->group[chat->numpeers].last_recv = unix_time(); chat->group[chat->numpeers].last_recv_msgping = unix_time(); ++chat->numpeers; + + if (chat->peer_namelistchange != NULL) + (*chat->peer_namelistchange)(chat, chat->numpeers - 1, CHAT_CHANGE_PEER_ADD, chat->group_namelistchange_userdata); + return (chat->numpeers - 1); } /* - * Delete a peer to the group chat. + * Delete a peer from the group chat. * * return 0 if success * return -1 if error. */ -static int delpeer(Group_Chat *chat, uint8_t *client_id) +static int delpeer(Group_Chat *chat, int peernum) { + if ((uint32_t)peernum >= chat->numpeers) + return -1; + uint32_t i; - Group_Peer *temp; - - for (i = 0; i < chat->numpeers; ++i) { - /* Equal */ - if (id_equal(chat->group[i].client_id, client_id)) { - --chat->numpeers; - - if (chat->numpeers != i) - id_copy(chat->group[i].client_id, chat->group[chat->numpeers].client_id); - - temp = realloc(chat->group, sizeof(Group_Peer) * (chat->numpeers)); - - if (temp == NULL) - return -1; - - chat->group = temp; - return 0; + for (i = 0; i < GROUP_CLOSE_CONNECTIONS; ++i) { /* If peer is in close list, time it out forcefully. */ + if (id_equal(chat->close[i].client_id, chat->group[peernum].client_id)) { + chat->close[i].last_recv = 0; + break; } } - return -1; + Group_Peer *temp; + --chat->numpeers; + + if (chat->numpeers == 0) { + free(chat->group); + chat->group = NULL; + return 0; + } + + if (chat->numpeers != (uint32_t)peernum) + memcpy(&chat->group[peernum], &chat->group[chat->numpeers], sizeof(Group_Peer)); + + temp = realloc(chat->group, sizeof(Group_Peer) * (chat->numpeers)); + + if (temp == NULL) + return -1; + + chat->group = temp; + + if (chat->peer_namelistchange != NULL) { + (*chat->peer_namelistchange)(chat, peernum, CHAT_CHANGE_PEER_DEL, chat->group_namelistchange_userdata); + } + + return 0; } /* Copy the name of peernum to name. @@ -262,25 +281,44 @@ int group_peername(Group_Chat *chat, int peernum, uint8_t *name) return -1; if (chat->group[peernum].nick_len == 0) { - memcpy(name, "NSA Agent", 10); /* Kindly remind the user that someone with no name might be an NSA agent.*/ - return 10; + /* memcpy(name, "NSA agent", 10); */ /* Srsly? */ /* Kindly remind the user that someone with no name might be a moronic NSA agent.*/ + name[0] = 0; + return 0; } memcpy(name, chat->group[peernum].nick, chat->group[peernum].nick_len); return chat->group[peernum].nick_len; } +static void setnick(Group_Chat *chat, int peernum, uint8_t *contents, uint16_t contents_len) +{ + if (contents_len > MAX_NICK_BYTES || contents_len == 0) + return; + + /* same name as already stored? */ + if (chat->group[peernum].nick_len == contents_len) + if (!memcmp(chat->group[peernum].nick, contents, contents_len)) + return; + + memcpy(chat->group[peernum].nick, contents, contents_len); + /* Force null termination */ + chat->group[peernum].nick[contents_len - 1] = 0; + chat->group[peernum].nick_len = contents_len; + + if (chat->peer_namelistchange != NULL) + (*chat->peer_namelistchange)(chat, peernum, CHAT_CHANGE_PEER_NAME, chat->group_namelistchange_userdata); +} /* min time between pings sent to one peer in seconds */ /* TODO: move this to global section */ -#define PING_TIMEOUT 5 +#define GROUP_PING_TIMEOUT 5 static int send_getnodes(Group_Chat *chat, IP_Port ip_port, int peernum) { if ((uint32_t)peernum >= chat->numpeers) return -1; - if (!is_timeout(chat->group[peernum].last_pinged, PING_TIMEOUT)) + if (!is_timeout(chat->group[peernum].last_pinged, GROUP_PING_TIMEOUT)) return -1; getnodes_data contents; @@ -288,6 +326,15 @@ static int send_getnodes(Group_Chat *chat, IP_Port ip_port, int peernum) chat->group[peernum].last_pinged = unix_time(); chat->group[peernum].pingid = contents.pingid; + chat->group[peernum].ping_via = ip_port; + + if (chat->assoc) { + IPPTs ippts; + ippts.timestamp = unix_time(); + ippts.ip_port = ip_port; + + Assoc_add_entry(chat->assoc, chat->group[peernum].client_id, &ippts, NULL, 1); + } return send_groupchatpacket(chat, ip_port, chat->group[peernum].client_id, (uint8_t *)&contents, sizeof(contents), CRYPTO_PACKET_GROUP_CHAT_GET_NODES); @@ -343,7 +390,7 @@ static int handle_sendnodes(Group_Chat *chat, IP_Port source, int peernum, uint8 if ((len - sizeof(uint64_t)) % sizeof(groupchat_nodes) != 0) return 1; - if (is_timeout(chat->group[peernum].last_pinged, PING_TIMEOUT)) + if (is_timeout(chat->group[peernum].last_pinged, GROUP_PING_TIMEOUT)) return 1; sendnodes_data contents; @@ -355,6 +402,9 @@ static int handle_sendnodes(Group_Chat *chat, IP_Port source, int peernum, uint8 uint16_t numnodes = (len - sizeof(contents.pingid)) / sizeof(groupchat_nodes); uint32_t i; + IPPTs ippts_send; + ippts_send.timestamp = unix_time(); + for (i = 0; i < numnodes; ++i) { if (peer_okping(chat, contents.nodes[i].client_id) > 0) { int peern = peer_in_chat(chat, contents.nodes[i].client_id); @@ -367,14 +417,31 @@ static int handle_sendnodes(Group_Chat *chat, IP_Port source, int peernum, uint8 continue; send_getnodes(chat, contents.nodes[i].ip_port, peern); + + if (chat->assoc) { + ippts_send.ip_port = contents.nodes[i].ip_port; + Assoc_add_entry(chat->assoc, contents.nodes[i].client_id, &ippts_send, NULL, 0); + } } } - add_closepeer(chat, chat->group[peernum].client_id, source); + int ok = add_closepeer(chat, chat->group[peernum].client_id, source); + + if (chat->assoc) { + ippts_send.ip_port = chat->group[peernum].ping_via; + ippts_send.timestamp = chat->group[peernum].last_pinged; + + IP_Port ipp_recv; + ipp_recv = source; + + Assoc_add_entry(chat->assoc, contents.nodes[i].client_id, &ippts_send, &ipp_recv, ok == 0 ? 1 : 0); + } + return 0; } #define GROUP_DATA_MIN_SIZE (crypto_box_PUBLICKEYBYTES + sizeof(uint32_t) + 1) +static void send_names_new_peer(Group_Chat *chat); static int handle_data(Group_Chat *chat, uint8_t *data, uint32_t len) { @@ -385,7 +452,8 @@ static int handle_data(Group_Chat *chat, uint8_t *data, uint32_t len) int peernum = peer_in_chat(chat, data); if (peernum == -1) { /*NOTE: This is just for testing and will be removed later.*/ - peernum = addpeer(chat, data); + if (data[crypto_box_PUBLICKEYBYTES + sizeof(uint32_t)] != GROUP_CHAT_QUIT) + peernum = addpeer(chat, data); } if (peernum == -1) @@ -427,6 +495,21 @@ static int handle_data(Group_Chat *chat, uint8_t *data, uint32_t len) return 1; addpeer(chat, contents); + send_names_new_peer(chat); + break; + + case GROUP_CHAT_QUIT: /* If peer tells us he is quitting */ + if (contents_len != 0) + return 1; + + delpeer(chat, peernum); + break; + + case GROUP_CHAT_PEER_NICK: + if (contents_len > MAX_NICK_BYTES || contents_len == 0) + return 1; + + setnick(chat, peernum, contents, contents_len); break; case GROUP_CHAT_CHAT_MESSAGE: /* If message is chat message */ @@ -521,6 +604,30 @@ uint32_t group_sendmessage(Group_Chat *chat, uint8_t *message, uint32_t length) return send_data(chat, message, length, GROUP_CHAT_CHAT_MESSAGE); //TODO: better return values? } +/* + * Send id/nick combo to the group. + * + * returns the number of peers it has sent it to. + */ +static uint32_t group_send_nick(Group_Chat *chat, uint8_t *nick, uint16_t nick_len) +{ + if (nick_len > MAX_NICK_BYTES) + return 0; + + return send_data(chat, nick, nick_len, GROUP_CHAT_PEER_NICK); +} + +int set_nick(Group_Chat *chat, uint8_t *nick, uint16_t nick_len) +{ + if (nick_len > MAX_NICK_BYTES || nick_len == 0) + return -1; + + memcpy(chat->nick, nick, nick_len); + chat->nick_len = nick_len; + group_send_nick(chat, chat->nick, chat->nick_len); + return 0; +} + uint32_t group_newpeer(Group_Chat *chat, uint8_t *client_id) { addpeer(chat, client_id); @@ -534,6 +641,26 @@ void callback_groupmessage(Group_Chat *chat, void (*function)(Group_Chat *chat, chat->group_message_userdata = userdata; } +void callback_namelistchange(Group_Chat *chat, void (*function)(Group_Chat *chat, int peer, uint8_t change, void *), void *userdata) +{ + chat->peer_namelistchange = function; + chat->group_namelistchange_userdata = userdata; +} + +uint32_t group_numpeers(Group_Chat *chat) +{ + return chat->numpeers; +} + +uint32_t group_client_names(Group_Chat *chat, uint8_t names[][MAX_NICK_BYTES], uint16_t length) +{ + uint32_t i; + for (i = 0; i < chat->numpeers && i < length; ++i) { + group_peername(chat, i, names[i]); + } + return i; +} + Group_Chat *new_groupchat(Networking_Core *net) { unix_time_update(); @@ -544,6 +671,10 @@ Group_Chat *new_groupchat(Networking_Core *net) Group_Chat *chat = calloc(1, sizeof(Group_Chat)); chat->net = net; crypto_box_keypair(chat->self_public_key, chat->self_secret_key); + + /* (2^4) * 5 = 80 entries seems to be a moderate size */ + chat->assoc = new_Assoc(4, 5, chat->self_public_key); + return chat; } @@ -554,7 +685,6 @@ static void ping_close(Group_Chat *chat) uint32_t i; for (i = 0; i < GROUP_CLOSE_CONNECTIONS; ++i) { - /* previous condition was always true, assuming this is the wanted one: */ if (!is_timeout(chat->close[i].last_recv, BAD_GROUPNODE_TIMEOUT)) { int peernum = peer_in_chat(chat, chat->close[i].client_id); @@ -583,12 +713,32 @@ static void del_dead_peers(Group_Chat *chat) uint32_t i; for (i = 0; i < chat->numpeers; ++i) { - if (is_timeout(chat->group[i].last_recv_msgping, GROUP_PING_INTERVAL * 2)) { - delpeer(chat, chat->group[i].client_id); + if (is_timeout(chat->group[i].last_recv_msgping, GROUP_PING_INTERVAL * 4)) { + delpeer(chat, i); } } } +#define NICK_SEND_INTERVAL 180 +static void send_names_new_peer(Group_Chat *chat) +{ + group_send_nick(chat, chat->nick, chat->nick_len); + chat->last_sent_nick = (unix_time() - NICK_SEND_INTERVAL) + 10; +} +static void send_names(Group_Chat *chat) +{ + /* send own nick from time to time, to let newly added peers be informed + * first time only: use a shorter timeframe, because we might not be in our own + * peer list yet */ + if (is_timeout(chat->last_sent_nick, 180)) + if (group_send_nick(chat, chat->nick, chat->nick_len) > 0) { + if (!chat->last_sent_nick) + chat->last_sent_nick = (unix_time() - NICK_SEND_INTERVAL) + 10; + else + chat->last_sent_nick = unix_time(); + } +} + void do_groupchat(Group_Chat *chat) { unix_time_update(); @@ -596,10 +746,12 @@ void do_groupchat(Group_Chat *chat) ping_group(chat); /* TODO: Maybe run this less? */ del_dead_peers(chat); + send_names(chat); } void kill_groupchat(Group_Chat *chat) { + send_data(chat, 0, 0, GROUP_CHAT_QUIT); free(chat->group); free(chat); } diff --git a/toxcore/group_chats.h b/toxcore/group_chats.h index b3f2e5a8..33773785 100644 --- a/toxcore/group_chats.h +++ b/toxcore/group_chats.h @@ -33,6 +33,7 @@ typedef struct { uint8_t client_id[crypto_box_PUBLICKEYBYTES]; uint64_t pingid; uint64_t last_pinged; + IP_Port ping_via; uint64_t last_recv; uint64_t last_recv_msgping; @@ -46,7 +47,6 @@ typedef struct { uint8_t client_id[crypto_box_PUBLICKEYBYTES]; IP_Port ip_port; uint64_t last_recv; - } Group_Close; #define GROUP_CLOSE_CONNECTIONS 6 @@ -63,12 +63,22 @@ typedef struct Group_Chat { uint32_t message_number; void (*group_message)(struct Group_Chat *m, int, uint8_t *, uint16_t, void *); void *group_message_userdata; + void (*peer_namelistchange)(struct Group_Chat *m, int peer, uint8_t change, void *); + void *group_namelistchange_userdata; + uint64_t last_sent_ping; + uint8_t nick[MAX_NICK_BYTES]; + uint16_t nick_len; + uint64_t last_sent_nick; + + struct Assoc *assoc; } Group_Chat; #define GROUP_CHAT_PING 0 #define GROUP_CHAT_NEW_PEER 16 +#define GROUP_CHAT_QUIT 24 +#define GROUP_CHAT_PEER_NICK 48 #define GROUP_CHAT_CHAT_MESSAGE 64 /* Copy the name of peernum to name. @@ -84,9 +94,22 @@ int group_peername(Group_Chat *chat, int peernum, uint8_t *name); * * format of function is: function(Group_Chat *chat, peer number, message, message length, userdata) */ - void callback_groupmessage(Group_Chat *chat, void (*function)(Group_Chat *chat, int, uint8_t *, uint16_t, void *), void *userdata); +/* + * Set callback function for peer name list changes. + * + * It gets called every time the name list changes(new peer/name, deleted peer) + * + * format of function is: function(Group_Chat *chat, userdata) + */ +typedef enum { + CHAT_CHANGE_PEER_ADD, + CHAT_CHANGE_PEER_DEL, + CHAT_CHANGE_PEER_NAME, +} CHAT_CHANGE; + +void callback_namelistchange(Group_Chat *chat, void (*function)(Group_Chat *chat, int peer, uint8_t change, void *), void *userdata); /* * Send a message to the group. @@ -95,6 +118,12 @@ void callback_groupmessage(Group_Chat *chat, void (*function)(Group_Chat *chat, */ uint32_t group_sendmessage(Group_Chat *chat, uint8_t *message, uint32_t length); +/* + * Set our nick for this group. + * + * returns -1 on failure, 0 on success. + */ +int set_nick(Group_Chat *chat, uint8_t *nick, uint16_t nick_len); /* * Tell everyone about a new peer (a person we are inviting for example.) @@ -112,6 +141,18 @@ uint32_t group_newpeer(Group_Chat *chat, uint8_t *client_id); Group_Chat *new_groupchat(Networking_Core *net); +/* Return the number of peers in the group chat. + */ +uint32_t group_numpeers(Group_Chat *chat); + +/* List all the peers in the group chat. + * + * Copies the names of the peers to the name[length][MAX_NICK_BYTES] array. + * + * returns the number of peers. + */ +uint32_t group_client_names(Group_Chat *chat, uint8_t names[][MAX_NICK_BYTES], uint16_t length); + /* Kill a group chat * * Frees the memory and everything. diff --git a/toxcore/network.c b/toxcore/network.c index 5cbc4695..951475f8 100644 --- a/toxcore/network.c +++ b/toxcore/network.c @@ -310,9 +310,12 @@ typedef struct { int networking_wait_prepare(Networking_Core *net, uint32_t sendqueue_length, uint8_t *data, uint16_t *lenptr) { - if ((data == NULL) || (*lenptr < sizeof(select_info))) { - *lenptr = sizeof(select_info); - return 0; + if ((data == NULL) || !lenptr || (*lenptr < sizeof(select_info))) { + if (lenptr) { + *lenptr = sizeof(select_info); + return 0; + } else + return -1; } *lenptr = sizeof(select_info); @@ -404,6 +407,10 @@ static int at_startup(void) if (at_startup_ran != 0) return 0; +#ifndef VANILLA_NACL + sodium_init(); +#endif + #ifdef WIN32 WSADATA wsaData; @@ -678,12 +685,7 @@ int ip_equal(IP *a, IP *b) if (a->family == AF_INET) return (a->ip4.in_addr.s_addr == b->ip4.in_addr.s_addr); else if (a->family == AF_INET6) -#ifdef WIN32 - return IN6_ADDR_EQUAL(&a->ip6.in6_addr, &b->ip6.in6_addr); - -#else return IN6_ARE_ADDR_EQUAL(&a->ip6.in6_addr, &b->ip6.in6_addr); -#endif else return 0; } diff --git a/toxcore/network.h b/toxcore/network.h index bb851dcb..88cfaf17 100644 --- a/toxcore/network.h +++ b/toxcore/network.h @@ -43,6 +43,18 @@ typedef unsigned int sock_t; /* sa_family_t is the sockaddr_in / sockaddr_in6 family field */ typedef short sa_family_t; +#ifndef IN6_ARE_ADDR_EQUAL +#ifdef IN6_ADDR_EQUAL +#define IN6_ARE_ADDR_EQUAL(a,b) IN6_ADDR_EQUAL(a,b) +#else +#define IN6_ARE_ADDR_EQUAL(a,b) \ + ((((__const uint32_t *) (a))[0] == ((__const uint32_t *) (b))[0]) \ + && (((__const uint32_t *) (a))[1] == ((__const uint32_t *) (b))[1]) \ + && (((__const uint32_t *) (a))[2] == ((__const uint32_t *) (b))[2]) \ + && (((__const uint32_t *) (a))[3] == ((__const uint32_t *) (b))[3])) +#endif +#endif + #ifndef EWOULDBLOCK #define EWOULDBLOCK WSAEWOULDBLOCK #endif @@ -134,7 +146,7 @@ typedef union { uint8_t uint8[8]; } IP4_Port; -typedef struct { +typedef struct IP_Port { IP ip; uint16_t port; } IP_Port; diff --git a/toxcore/ping.c b/toxcore/ping.c index 80e85a45..e3db6ed1 100644 --- a/toxcore/ping.c +++ b/toxcore/ping.c @@ -27,14 +27,16 @@ #include "config.h" #endif -#include #include -#include "net_crypto.h" #include "DHT.h" +#include "assoc.h" +#include "ping.h" + +#include "network.h" +#include "util.h" #define PING_NUM_MAX 384 -#define PING_TIMEOUT 5 // 5s /* Ping newly announced nodes to ping per TIME_TOPING seconds*/ #define TIME_TOPING 5 @@ -45,7 +47,7 @@ typedef struct { uint64_t timestamp; } pinged_t; -typedef struct { +struct PING { Net_Crypto *c; pinged_t pings[PING_NUM_MAX]; @@ -54,15 +56,9 @@ typedef struct { Node_format toping[MAX_TOPING]; uint64_t last_toping; -} PING; +}; -#define __PING_C__ - -#include "network.h" -#include "util.h" -#include "ping.h" - -static bool is_ping_timeout(uint64_t time) +static int is_ping_timeout(uint64_t time) { return is_timeout(time, PING_TIMEOUT); } @@ -265,7 +261,16 @@ static int handle_ping_response(void *_dht, IP_Port source, uint8_t *packet, uin return 1; /* Associate client_id with the ip the request was sent to */ - addto_lists(dht, ping->pings[ping_index - 1].ip_port, packet + 1); + int used = addto_lists(dht, ping->pings[ping_index - 1].ip_port, packet + 1); + + if (dht->assoc) { + IPPTs ippts; + ippts.ip_port = ping->pings[ping_index - 1].ip_port; + ippts.timestamp = ping->pings[ping_index - 1].timestamp; + + Assoc_add_entry(dht->assoc, packet + 1, &ippts, &source, used > 0 ? 1 : 0); + } + return 0; } diff --git a/toxcore/ping.h b/toxcore/ping.h index c2437e1b..00e1c697 100644 --- a/toxcore/ping.h +++ b/toxcore/ping.h @@ -24,11 +24,7 @@ #ifndef __PING_H__ #define __PING_H__ -#include - -#ifndef __PING_C__ typedef struct PING PING; -#endif /* Add nodes to the toping list. * All nodes in this list are pinged every TIME_TOPING seconds diff --git a/toxcore/tox.c b/toxcore/tox.c index 6a0c6a62..487f2517 100644 --- a/toxcore/tox.c +++ b/toxcore/tox.c @@ -37,7 +37,7 @@ typedef struct Messenger Tox; * Format: [client_id (32 bytes)][nospam number (4 bytes)][checksum (2 bytes)] * */ -void tox_getaddress(Tox *tox, uint8_t *address) +void tox_get_address(Tox *tox, uint8_t *address) { Messenger *m = tox; getaddress(m, address); @@ -60,7 +60,7 @@ void tox_getaddress(Tox *tox, uint8_t *address) * (the nospam for that friend was set to the new one). * return FAERR_NOMEM if increasing the friend list size fails. */ -int tox_addfriend(Tox *tox, uint8_t *address, uint8_t *data, uint16_t length) +int tox_add_friend(Tox *tox, uint8_t *address, uint8_t *data, uint16_t length) { Messenger *m = tox; return m_addfriend(m, address, data, length); @@ -71,7 +71,7 @@ int tox_addfriend(Tox *tox, uint8_t *address, uint8_t *data, uint16_t length) * return the friend number if success. * return -1 if failure. */ -int tox_addfriend_norequest(Tox *tox, uint8_t *client_id) +int tox_add_friend_norequest(Tox *tox, uint8_t *client_id) { Messenger *m = tox; return m_addfriend_norequest(m, client_id); @@ -80,7 +80,7 @@ int tox_addfriend_norequest(Tox *tox, uint8_t *client_id) /* return the friend id associated to that client id. * return -1 if no such friend. */ -int tox_getfriend_id(Tox *tox, uint8_t *client_id) +int tox_get_friend_id(Tox *tox, uint8_t *client_id) { Messenger *m = tox; return getfriend_id(m, client_id); @@ -92,14 +92,14 @@ int tox_getfriend_id(Tox *tox, uint8_t *client_id) * return 0 if success. * return -1 if failure. */ -int tox_getclient_id(Tox *tox, int friend_id, uint8_t *client_id) +int tox_get_client_id(Tox *tox, int friend_id, uint8_t *client_id) { Messenger *m = tox; return getclient_id(m, friend_id, client_id); } /* Remove a friend. */ -int tox_delfriend(Tox *tox, int friendnumber) +int tox_del_friend(Tox *tox, int friendnumber) { Messenger *m = tox; return m_delfriend(m, friendnumber); @@ -111,7 +111,7 @@ int tox_delfriend(Tox *tox, int friendnumber) * return 0 if friend is not connected to us (Offline). * return -1 on failure. */ -int tox_get_friend_connectionstatus(Tox *tox, int friendnumber) +int tox_get_friend_connection_status(Tox *tox, int friendnumber) { Messenger *m = tox; return m_get_friend_connectionstatus(m, friendnumber); @@ -137,13 +137,13 @@ int tox_friend_exists(Tox *tox, int friendnumber) * m_sendmessage_withid will send a message with the id of your choosing, * however we can generate an id for you by calling plain m_sendmessage. */ -uint32_t tox_sendmessage(Tox *tox, int friendnumber, uint8_t *message, uint32_t length) +uint32_t tox_send_message(Tox *tox, int friendnumber, uint8_t *message, uint32_t length) { Messenger *m = tox; return m_sendmessage(m, friendnumber, message, length); } -uint32_t tox_sendmessage_withid(Tox *tox, int friendnumber, uint32_t theid, uint8_t *message, uint32_t length) +uint32_t tox_send_message_withid(Tox *tox, int friendnumber, uint32_t theid, uint8_t *message, uint32_t length) { Messenger *m = tox; return m_sendmessage_withid(m, friendnumber, theid, message, length); @@ -159,13 +159,13 @@ uint32_t tox_sendmessage_withid(Tox *tox, int friendnumber, uint32_t theid, uint * m_sendaction_withid will send an action message with the id of your choosing, * however we can generate an id for you by calling plain m_sendaction. */ -uint32_t tox_sendaction(Tox *tox, int friendnumber, uint8_t *action, uint32_t length) +uint32_t tox_send_action(Tox *tox, int friendnumber, uint8_t *action, uint32_t length) { Messenger *m = tox; return m_sendaction(m, friendnumber, action, length); } -uint32_t tox_sendaction_withid(Tox *tox, int friendnumber, uint32_t theid, uint8_t *action, uint32_t length) +uint32_t tox_send_action_withid(Tox *tox, int friendnumber, uint32_t theid, uint8_t *action, uint32_t length) { Messenger *m = tox; return m_sendaction_withid(m, friendnumber, theid, action, length); @@ -179,7 +179,7 @@ uint32_t tox_sendaction_withid(Tox *tox, int friendnumber, uint32_t theid, uint8 * return 0 if success. * return -1 if failure. */ -int tox_setname(Tox *tox, uint8_t *name, uint16_t length) +int tox_set_name(Tox *tox, uint8_t *name, uint16_t length) { Messenger *m = tox; return setname(m, name, length); @@ -193,7 +193,7 @@ int tox_setname(Tox *tox, uint8_t *name, uint16_t length) * return length of the name. * return 0 on error. */ -uint16_t tox_getselfname(Tox *tox, uint8_t *name, uint16_t nlen) +uint16_t tox_get_self_name(Tox *tox, uint8_t *name, uint16_t nlen) { Messenger *m = tox; return getself_name(m, name, nlen); @@ -205,7 +205,7 @@ uint16_t tox_getselfname(Tox *tox, uint8_t *name, uint16_t nlen) * return length of name (with the NULL terminator) if success. * return -1 if failure. */ -int tox_getname(Tox *tox, int friendnumber, uint8_t *name) +int tox_get_name(Tox *tox, int friendnumber, uint8_t *name) { Messenger *m = tox; return getname(m, friendnumber, name); @@ -216,13 +216,13 @@ int tox_getname(Tox *tox, int friendnumber, uint8_t *name) * * return 0 on success, -1 on failure. */ -int tox_set_statusmessage(Tox *tox, uint8_t *status, uint16_t length) +int tox_set_status_message(Tox *tox, uint8_t *status, uint16_t length) { Messenger *m = tox; return m_set_statusmessage(m, status, length); } -int tox_set_userstatus(Tox *tox, TOX_USERSTATUS status) +int tox_set_user_status(Tox *tox, TOX_USERSTATUS status) { Messenger *m = tox; return m_set_userstatus(m, (USERSTATUS)status); @@ -231,7 +231,7 @@ int tox_set_userstatus(Tox *tox, TOX_USERSTATUS status) /* return the length of friendnumber's status message, including null. * Pass it into malloc. */ -int tox_get_statusmessage_size(Tox *tox, int friendnumber) +int tox_get_status_message_size(Tox *tox, int friendnumber) { Messenger *m = tox; return m_get_statusmessage_size(m, friendnumber); @@ -241,13 +241,13 @@ int tox_get_statusmessage_size(Tox *tox, int friendnumber) * Get the size you need to allocate from m_get_statusmessage_size. * The self variant will copy our own status message. */ -int tox_copy_statusmessage(Tox *tox, int friendnumber, uint8_t *buf, uint32_t maxlen) +int tox_get_status_message(Tox *tox, int friendnumber, uint8_t *buf, uint32_t maxlen) { Messenger *m = tox; return m_copy_statusmessage(m, friendnumber, buf, maxlen); } -int tox_copy_self_statusmessage(Tox *tox, uint8_t *buf, uint32_t maxlen) +int tox_get_self_status_message(Tox *tox, uint8_t *buf, uint32_t maxlen) { Messenger *m = tox; return m_copy_self_statusmessage(m, buf, maxlen); @@ -258,13 +258,13 @@ int tox_copy_self_statusmessage(Tox *tox, uint8_t *buf, uint32_t maxlen) * As above, the self variant will return our own USERSTATUS. * If friendnumber is invalid, this shall return USERSTATUS_INVALID. */ -TOX_USERSTATUS tox_get_userstatus(Tox *tox, int friendnumber) +TOX_USERSTATUS tox_get_user_status(Tox *tox, int friendnumber) { Messenger *m = tox; return (TOX_USERSTATUS)m_get_userstatus(m, friendnumber); } -TOX_USERSTATUS tox_get_selfuserstatus(Tox *tox) +TOX_USERSTATUS tox_get_self_user_status(Tox *tox) { Messenger *m = tox; return (TOX_USERSTATUS)m_get_self_userstatus(m); @@ -294,7 +294,7 @@ uint32_t tox_count_friendlist(Tox *tox) * Otherwise, returns the number of elements copied. * If the array was too small, the contents * of out_list will be truncated to list_size. */ -uint32_t tox_copy_friendlist(Tox *tox, int *out_list, uint32_t list_size) +uint32_t tox_get_friendlist(Tox *tox, int *out_list, uint32_t list_size) { Messenger *m = tox; return copy_friendlist(m, out_list, list_size); @@ -303,7 +303,7 @@ uint32_t tox_copy_friendlist(Tox *tox, int *out_list, uint32_t list_size) /* Set the function that will be executed when a friend request is received. * Function format is function(uint8_t * public_key, uint8_t * data, uint16_t length) */ -void tox_callback_friendrequest(Tox *tox, void (*function)(uint8_t *, uint8_t *, uint16_t, void *), void *userdata) +void tox_callback_friend_request(Tox *tox, void (*function)(uint8_t *, uint8_t *, uint16_t, void *), void *userdata) { Messenger *m = tox; m_callback_friendrequest(m, function, userdata); @@ -313,7 +313,7 @@ void tox_callback_friendrequest(Tox *tox, void (*function)(uint8_t *, uint8_t *, /* Set the function that will be executed when a message from a friend is received. * Function format is: function(int friendnumber, uint8_t * message, uint32_t length) */ -void tox_callback_friendmessage(Tox *tox, void (*function)(Messenger *tox, int, uint8_t *, uint16_t, void *), +void tox_callback_friend_message(Tox *tox, void (*function)(Messenger *tox, int, uint8_t *, uint16_t, void *), void *userdata) { Messenger *m = tox; @@ -333,7 +333,7 @@ void tox_callback_action(Tox *tox, void (*function)(Messenger *tox, int, uint8_t * function(int friendnumber, uint8_t *newname, uint16_t length) * You are not responsible for freeing newname. */ -void tox_callback_namechange(Tox *tox, void (*function)(Messenger *tox, int, uint8_t *, uint16_t, void *), +void tox_callback_name_change(Tox *tox, void (*function)(Messenger *tox, int, uint8_t *, uint16_t, void *), void *userdata) { Messenger *m = tox; @@ -344,7 +344,7 @@ void tox_callback_namechange(Tox *tox, void (*function)(Messenger *tox, int, uin * function(int friendnumber, uint8_t *newstatus, uint16_t length) * You are not responsible for freeing newstatus. */ -void tox_callback_statusmessage(Tox *tox, void (*function)(Messenger *tox, int, uint8_t *, uint16_t, void *), +void tox_callback_status_message(Tox *tox, void (*function)(Messenger *tox, int, uint8_t *, uint16_t, void *), void *userdata) { Messenger *m = tox; @@ -354,7 +354,7 @@ void tox_callback_statusmessage(Tox *tox, void (*function)(Messenger *tox, int, /* Set the callback for status type changes. * function(int friendnumber, USERSTATUS kind) */ -void tox_callback_userstatus(Tox *tox, void (*_function)(Tox *tox, int, TOX_USERSTATUS, void *), void *userdata) +void tox_callback_user_status(Tox *tox, void (*_function)(Tox *tox, int, TOX_USERSTATUS, void *), void *userdata) { Messenger *m = tox; typedef void (*function_type)(Messenger *, int, USERSTATUS, void *); @@ -388,7 +388,7 @@ void tox_callback_read_receipt(Tox *tox, void (*function)(Messenger *tox, int, u * being previously online" part. It's assumed that when adding friends, * their connection status is offline. */ -void tox_callback_connectionstatus(Tox *tox, void (*function)(Messenger *tox, int, uint8_t, void *), void *userdata) +void tox_callback_connection_status(Tox *tox, void (*function)(Messenger *tox, int, uint8_t, void *), void *userdata) { Messenger *m = tox; m_callback_connectionstatus(m, function, userdata); @@ -415,6 +415,19 @@ void tox_callback_group_message(Tox *tox, void (*function)(Messenger *tox, int, Messenger *m = tox; m_callback_group_message(m, function, userdata); } + +/* Set callback function for peer name list changes. + * + * It gets called every time the name list changes(new peer/name, deleted peer) + * Function(Tox *tox, int groupnumber, void *userdata) + */ + +void tox_callback_group_namelist_change(Tox *tox, void (*function)(Tox *tox, int, int, uint8_t, void *), void *userdata) +{ + Messenger *m = tox; + m_callback_group_namelistchange(m, function, userdata); +} + /* Creates a new groupchat and puts it in the chats array. * * return group number on success. @@ -477,6 +490,48 @@ int tox_group_message_send(Tox *tox, int groupnumber, uint8_t *message, uint32_t return group_message_send(m, groupnumber, message, length); } +/* Return the number of peers in the group chat on success. + * return -1 on failure + */ +int tox_group_number_peers(Tox *tox, int groupnumber) +{ + Messenger *m = tox; + return group_number_peers(m, groupnumber); +} + +/* List all the peers in the group chat. + * + * Copies the names of the peers to the name[length][MAX_NICK_BYTES] array. + * + * returns the number of peers on success. + * + * return -1 on failure. + */ +int tox_group_get_names(Tox *tox, int groupnumber, uint8_t names[][TOX_MAX_NAME_LENGTH], uint16_t length) +{ + Messenger *m = tox; + return group_names(m, groupnumber, names, length); +} + +/* Return the number of chats in the instance m. + * You should use this to determine how much memory to allocate + * for copy_chatlist. */ +uint32_t tox_count_chatlist(Tox *tox) +{ + Messenger *m = tox; + return count_chatlist(m); +} + +/* Copy a list of valid chat IDs into the array out_list. + * If out_list is NULL, returns 0. + * Otherwise, returns the number of elements copied. + * If the array was too small, the contents + * of out_list will be truncated to list_size. */ +uint32_t tox_get_chatlist(Tox *tox, int *out_list, uint32_t list_size) +{ + Messenger *m = tox; + return copy_chatlist(m, out_list, list_size); +} /****************FILE SENDING FUNCTIONS*****************/ @@ -486,7 +541,7 @@ int tox_group_message_send(Tox *tox, int groupnumber, uint8_t *message, uint32_t * * Function(Tox *tox, int friendnumber, uint8_t filenumber, uint64_t filesize, uint8_t *filename, uint16_t filename_length, void *userdata) */ -void tox_callback_file_sendrequest(Tox *tox, void (*function)(Messenger *tox, int, uint8_t, uint64_t, uint8_t *, +void tox_callback_file_send_request(Tox *tox, void (*function)(Messenger *tox, int, uint8_t, uint64_t, uint8_t *, uint16_t, void *), void *userdata) { @@ -522,7 +577,7 @@ void tox_callback_file_data(Tox *tox, void (*function)(Messenger *tox, int, uint * return file number on success * return -1 on failure */ -int tox_new_filesender(Tox *tox, int friendnumber, uint64_t filesize, uint8_t *filename, uint16_t filename_length) +int tox_new_file_sender(Tox *tox, int friendnumber, uint64_t filesize, uint8_t *filename, uint16_t filename_length) { Messenger *m = tox; return new_filesender(m, friendnumber, filesize, filename, filename_length); @@ -530,10 +585,10 @@ int tox_new_filesender(Tox *tox, int friendnumber, uint64_t filesize, uint8_t *f /* Send a file control request. * send_receive is 0 if we want the control packet to target a sending file, 1 if it targets a receiving file. * - * return 1 on success - * return 0 on failure + * return 0 on success + * return -1 on failure */ -int tox_file_sendcontrol(Tox *tox, int friendnumber, uint8_t send_receive, uint8_t filenumber, uint8_t message_id, +int tox_file_send_control(Tox *tox, int friendnumber, uint8_t send_receive, uint8_t filenumber, uint8_t message_id, uint8_t *data, uint16_t length) { Messenger *m = tox; @@ -541,21 +596,21 @@ int tox_file_sendcontrol(Tox *tox, int friendnumber, uint8_t send_receive, uint8 } /* Send file data. * - * return 1 on success - * return 0 on failure + * return 0 on success + * return -1 on failure */ -int tox_file_senddata(Tox *tox, int friendnumber, uint8_t filenumber, uint8_t *data, uint16_t length) +int tox_file_send_data(Tox *tox, int friendnumber, uint8_t filenumber, uint8_t *data, uint16_t length) { Messenger *m = tox; return file_data(m, friendnumber, filenumber, data, length); } -/* Returns the recommended/maximum size of the filedata you send with tox_file_senddata() +/* Returns the recommended/maximum size of the filedata you send with tox_file_send_data() * * return size on success - * return 0 on failure (currently will never return 0) + * return -1 on failure (currently will never return -1) */ -int tox_filedata_size(Tox *tox, int friendnumber) +int tox_file_data_size(Tox *tox, int friendnumber) { return MAX_DATA_SIZE - crypto_box_MACBYTES - 3; } @@ -567,7 +622,7 @@ int tox_filedata_size(Tox *tox, int friendnumber) * return number of bytes remaining to be sent/received on success * return 0 on failure */ -uint64_t tox_file_dataremaining(Tox *tox, int friendnumber, uint8_t filenumber, uint8_t send_receive) +uint64_t tox_file_data_remaining(Tox *tox, int friendnumber, uint8_t filenumber, uint8_t send_receive) { Messenger *m = tox; return file_dataremaining(m, friendnumber, filenumber, send_receive); diff --git a/toxcore/tox.h b/toxcore/tox.h index 13e9c7a9..9f84876e 100644 --- a/toxcore/tox.h +++ b/toxcore/tox.h @@ -129,7 +129,7 @@ typedef struct Tox Tox; /* return FRIEND_ADDRESS_SIZE byte address to give to others. * format: [client_id (32 bytes)][nospam number (4 bytes)][checksum (2 bytes)] */ -void tox_getaddress(Tox *tox, uint8_t *address); +void tox_get_address(Tox *tox, uint8_t *address); /* Add a friend. * Set the data that will be sent along with friend request. @@ -147,28 +147,28 @@ void tox_getaddress(Tox *tox, uint8_t *address); * (the nospam for that friend was set to the new one). * return TOX_FAERR_NOMEM if increasing the friend list size fails. */ -int tox_addfriend(Tox *tox, uint8_t *address, uint8_t *data, uint16_t length); +int tox_add_friend(Tox *tox, uint8_t *address, uint8_t *data, uint16_t length); /* Add a friend without sending a friendrequest. * return the friend number if success. * return -1 if failure. */ -int tox_addfriend_norequest(Tox *tox, uint8_t *client_id); +int tox_add_friend_norequest(Tox *tox, uint8_t *client_id); /* return the friend id associated to that client id. return -1 if no such friend */ -int tox_getfriend_id(Tox *tox, uint8_t *client_id); +int tox_get_friend_id(Tox *tox, uint8_t *client_id); /* Copies the public key associated to that friend id into client_id buffer. * Make sure that client_id is of size CLIENT_ID_SIZE. * return 0 if success. * return -1 if failure. */ -int tox_getclient_id(Tox *tox, int friend_id, uint8_t *client_id); +int tox_get_client_id(Tox *tox, int friend_id, uint8_t *client_id); /* Remove a friend. */ -int tox_delfriend(Tox *tox, int friendnumber); +int tox_del_friend(Tox *tox, int friendnumber); /* Checks friend's connecting status. * @@ -176,7 +176,7 @@ int tox_delfriend(Tox *tox, int friendnumber); * return 0 if friend is not connected to us (Offline). * return -1 on failure. */ -int tox_get_friend_connectionstatus(Tox *tox, int friendnumber); +int tox_get_friend_connection_status(Tox *tox, int friendnumber); /* Checks if there exists a friend with given friendnumber. * @@ -195,8 +195,8 @@ int tox_friend_exists(Tox *tox, int friendnumber); * m_sendmessage_withid will send a message with the id of your choosing, * however we can generate an id for you by calling plain m_sendmessage. */ -uint32_t tox_sendmessage(Tox *tox, int friendnumber, uint8_t *message, uint32_t length); -uint32_t tox_sendmessage_withid(Tox *tox, int friendnumber, uint32_t theid, uint8_t *message, uint32_t length); +uint32_t tox_send_message(Tox *tox, int friendnumber, uint8_t *message, uint32_t length); +uint32_t tox_send_message_withid(Tox *tox, int friendnumber, uint32_t theid, uint8_t *message, uint32_t length); /* Send an action to an online friend. * @@ -208,8 +208,8 @@ uint32_t tox_sendmessage_withid(Tox *tox, int friendnumber, uint32_t theid, uint * m_sendaction_withid will send an action message with the id of your choosing, * however we can generate an id for you by calling plain m_sendaction. */ -uint32_t tox_sendaction(Tox *tox, int friendnumber, uint8_t *action, uint32_t length); -uint32_t tox_sendaction_withid(Tox *tox, int friendnumber, uint32_t theid, uint8_t *action, uint32_t length); +uint32_t tox_send_action(Tox *tox, int friendnumber, uint8_t *action, uint32_t length); +uint32_t tox_send_action_withid(Tox *tox, int friendnumber, uint32_t theid, uint8_t *action, uint32_t length); /* Set our nickname. * name must be a string of maximum MAX_NAME_LENGTH length. @@ -219,7 +219,7 @@ uint32_t tox_sendaction_withid(Tox *tox, int friendnumber, uint32_t theid, uint8 * return 0 if success. * return -1 if failure. */ -int tox_setname(Tox *tox, uint8_t *name, uint16_t length); +int tox_set_name(Tox *tox, uint8_t *name, uint16_t length); /* * Get your nickname. @@ -230,7 +230,7 @@ int tox_setname(Tox *tox, uint8_t *name, uint16_t length); * return length of name. * return 0 on error. */ -uint16_t tox_getselfname(Tox *tox, uint8_t *name, uint16_t nlen); +uint16_t tox_get_self_name(Tox *tox, uint8_t *name, uint16_t nlen); /* Get name of friendnumber and put it in name. * name needs to be a valid memory location with a size of at least MAX_NAME_LENGTH (128) bytes. @@ -238,7 +238,7 @@ uint16_t tox_getselfname(Tox *tox, uint8_t *name, uint16_t nlen); * return length of name (with the NULL terminator) if success. * return -1 if failure. */ -int tox_getname(Tox *tox, int friendnumber, uint8_t *name); +int tox_get_name(Tox *tox, int friendnumber, uint8_t *name); /* Set our user status. * You are responsible for freeing status after. @@ -246,13 +246,13 @@ int tox_getname(Tox *tox, int friendnumber, uint8_t *name); * returns 0 on success. * returns -1 on failure. */ -int tox_set_statusmessage(Tox *tox, uint8_t *status, uint16_t length); -int tox_set_userstatus(Tox *tox, TOX_USERSTATUS status); +int tox_set_status_message(Tox *tox, uint8_t *status, uint16_t length); +int tox_set_user_status(Tox *tox, TOX_USERSTATUS status); /* return the length of friendnumber's status message, including null. * Pass it into malloc */ -int tox_get_statusmessage_size(Tox *tox, int friendnumber); +int tox_get_status_message_size(Tox *tox, int friendnumber); /* Copy friendnumber's status message into buf, truncating if size is over maxlen. * Get the size you need to allocate from m_get_statusmessage_size. @@ -261,16 +261,16 @@ int tox_get_statusmessage_size(Tox *tox, int friendnumber); * returns the length of the copied data on success * retruns -1 on failure. */ -int tox_copy_statusmessage(Tox *tox, int friendnumber, uint8_t *buf, uint32_t maxlen); -int tox_copy_self_statusmessage(Tox *tox, uint8_t *buf, uint32_t maxlen); +int tox_get_status_message(Tox *tox, int friendnumber, uint8_t *buf, uint32_t maxlen); +int tox_get_self_status_message(Tox *tox, uint8_t *buf, uint32_t maxlen); /* return one of USERSTATUS values. * Values unknown to your application should be represented as USERSTATUS_NONE. * As above, the self variant will return our own USERSTATUS. * If friendnumber is invalid, this shall return USERSTATUS_INVALID. */ -TOX_USERSTATUS tox_get_userstatus(Tox *tox, int friendnumber); -TOX_USERSTATUS tox_get_selfuserstatus(Tox *tox); +TOX_USERSTATUS tox_get_user_status(Tox *tox, int friendnumber); +TOX_USERSTATUS tox_get_self_user_status(Tox *tox); /* Sets whether we send read receipts for friendnumber. * This function is not lazy, and it will fail if yesno is not (0 or 1). @@ -287,17 +287,17 @@ uint32_t tox_count_friendlist(Tox *tox); * Otherwise, returns the number of elements copied. * If the array was too small, the contents * of out_list will be truncated to list_size. */ -uint32_t tox_copy_friendlist(Tox *tox, int *out_list, uint32_t list_size); +uint32_t tox_get_friendlist(Tox *tox, int *out_list, uint32_t list_size); /* Set the function that will be executed when a friend request is received. * Function format is function(uint8_t * public_key, uint8_t * data, uint16_t length) */ -void tox_callback_friendrequest(Tox *tox, void (*function)(uint8_t *, uint8_t *, uint16_t, void *), void *userdata); +void tox_callback_friend_request(Tox *tox, void (*function)(uint8_t *, uint8_t *, uint16_t, void *), void *userdata); /* Set the function that will be executed when a message from a friend is received. * Function format is: function(int friendnumber, uint8_t * message, uint32_t length) */ -void tox_callback_friendmessage(Tox *tox, void (*function)(Tox *tox, int, uint8_t *, uint16_t, void *), +void tox_callback_friend_message(Tox *tox, void (*function)(Tox *tox, int, uint8_t *, uint16_t, void *), void *userdata); /* Set the function that will be executed when an action from a friend is received. @@ -309,20 +309,20 @@ void tox_callback_action(Tox *tox, void (*function)(Tox *tox, int, uint8_t *, ui * function(int friendnumber, uint8_t *newname, uint16_t length) * You are not responsible for freeing newname */ -void tox_callback_namechange(Tox *tox, void (*function)(Tox *tox, int, uint8_t *, uint16_t, void *), +void tox_callback_name_change(Tox *tox, void (*function)(Tox *tox, int, uint8_t *, uint16_t, void *), void *userdata); /* Set the callback for status message changes. * function(int friendnumber, uint8_t *newstatus, uint16_t length) * You are not responsible for freeing newstatus. */ -void tox_callback_statusmessage(Tox *tox, void (*function)(Tox *tox, int, uint8_t *, uint16_t, void *), +void tox_callback_status_message(Tox *tox, void (*function)(Tox *tox, int, uint8_t *, uint16_t, void *), void *userdata); /* Set the callback for status type changes. * function(int friendnumber, USERSTATUS kind) */ -void tox_callback_userstatus(Tox *tox, void (*function)(Tox *tox, int, TOX_USERSTATUS, void *), void *userdata); +void tox_callback_user_status(Tox *tox, void (*function)(Tox *tox, int, TOX_USERSTATUS, void *), void *userdata); /* Set the callback for read receipts. * function(int friendnumber, uint32_t receipt) @@ -346,7 +346,7 @@ void tox_callback_read_receipt(Tox *tox, void (*function)(Tox *tox, int, uint32_ * being previously online" part. it's assumed that when adding friends, * their connection status is offline. */ -void tox_callback_connectionstatus(Tox *tox, void (*function)(Tox *tox, int, uint8_t, void *), void *userdata); +void tox_callback_connection_status(Tox *tox, void (*function)(Tox *tox, int, uint8_t, void *), void *userdata); /**********GROUP CHAT FUNCTIONS: WARNING WILL BREAK A LOT************/ @@ -363,6 +363,19 @@ void tox_callback_group_invite(Tox *tox, void (*function)(Tox *tox, int, uint8_t void tox_callback_group_message(Tox *tox, void (*function)(Tox *tox, int, int, uint8_t *, uint16_t, void *), void *userdata); +/* Set callback function for peer name list changes. + * + * It gets called every time the name list changes(new peer/name, deleted peer) + * Function(Tox *tox, int groupnumber, int peernumber, TOX_CHAT_CHANGE change, void *userdata) + */ +typedef enum { + TOX_CHAT_CHANGE_PEER_ADD, + TOX_CHAT_CHANGE_PEER_DEL, + TOX_CHAT_CHANGE_PEER_NAME, +} TOX_CHAT_CHANGE; + +void tox_callback_group_namelist_change(Tox *tox, void (*function)(Tox *tox, int, int, uint8_t, void *), void *userdata); + /* Creates a new groupchat and puts it in the chats array. * * return group number on success. @@ -398,31 +411,57 @@ int tox_invite_friend(Tox *tox, int friendnumber, int groupnumber); */ int tox_join_groupchat(Tox *tox, int friendnumber, uint8_t *friend_group_public_key); - /* send a group message * return 0 on success * return -1 on failure */ int tox_group_message_send(Tox *tox, int groupnumber, uint8_t *message, uint32_t length); +/* Return the number of peers in the group chat on success. + * return -1 on failure + */ +int tox_group_number_peers(Tox *tox, int groupnumber); + +/* List all the peers in the group chat. + * + * Copies the names of the peers to the name[length][TOX_MAX_NAME_LENGTH] array. + * + * returns the number of peers on success. + * + * return -1 on failure. + */ +int tox_group_get_names(Tox *tox, int groupnumber, uint8_t names[][TOX_MAX_NAME_LENGTH], uint16_t length); + +/* Return the number of chats in the instance m. + * You should use this to determine how much memory to allocate + * for copy_chatlist. */ +uint32_t tox_count_chatlist(Tox *tox); + +/* Copy a list of valid chat IDs into the array out_list. + * If out_list is NULL, returns 0. + * Otherwise, returns the number of elements copied. + * If the array was too small, the contents + * of out_list will be truncated to list_size. */ +uint32_t tox_get_chatlist(Tox *tox, int *out_list, uint32_t list_size); + /****************FILE SENDING FUNCTIONS*****************/ /* NOTE: This how to will be updated. * * HOW TO SEND FILES CORRECTLY: - * 1. Use tox_new_filesender(...) to create a new file sender. + * 1. Use tox_new_file_sender(...) to create a new file sender. * 2. Wait for the callback set with tox_callback_file_control(...) to be called with receive_send == 1 and control_type == TOX_FILECONTROL_ACCEPT - * 3. Send the data with tox_file_senddata(...) with chunk size tox_filedata_size(...) - * 4. When sending is done, send a tox_file_sendcontrol(...) with send_receive = 0 and message_id = TOX_FILECONTROL_FINISHED + * 3. Send the data with tox_file_send_data(...) with chunk size tox_file_data_size(...) + * 4. When sending is done, send a tox_file_send_control(...) with send_receive = 0 and message_id = TOX_FILECONTROL_FINISHED * * HOW TO RECEIVE FILES CORRECTLY: - * 1. wait for the callback set with tox_callback_file_sendrequest(...) - * 2. accept or refuse the connection with tox_file_sendcontrol(...) with send_receive = 1 and message_id = TOX_FILECONTROL_ACCEPT or TOX_FILECONTROL_KILL + * 1. wait for the callback set with tox_callback_file_send_request(...) + * 2. accept or refuse the connection with tox_file_send_control(...) with send_receive = 1 and message_id = TOX_FILECONTROL_ACCEPT or TOX_FILECONTROL_KILL * 3. save all the data received with the callback set with tox_callback_file_data(...) to a file. * 4. when the callback set with tox_callback_file_control(...) is called with receive_send == 0 and control_type == TOX_FILECONTROL_FINISHED * the file is done transferring. * - * tox_file_dataremaining(...) can be used to know how many bytes are left to send/receive. + * tox_file_data_remaining(...) can be used to know how many bytes are left to send/receive. * * If the connection breaks during file sending (The other person goes offline without pausing the sending and then comes back) * the reciever must send a control packet with receive_send == 0 message_id = TOX_FILECONTROL_RESUME_BROKEN and the data being @@ -445,7 +484,7 @@ enum { * * Function(Tox *tox, int friendnumber, uint8_t filenumber, uint64_t filesize, uint8_t *filename, uint16_t filename_length, void *userdata) */ -void tox_callback_file_sendrequest(Tox *tox, void (*function)(Tox *m, int, uint8_t, uint64_t, uint8_t *, uint16_t, +void tox_callback_file_send_request(Tox *tox, void (*function)(Tox *m, int, uint8_t, uint64_t, uint8_t *, uint16_t, void *), void *userdata); /* Set the callback for file control requests. @@ -473,32 +512,32 @@ void tox_callback_file_data(Tox *tox, void (*function)(Tox *m, int, uint8_t, uin * return file number on success * return -1 on failure */ -int tox_new_filesender(Tox *tox, int friendnumber, uint64_t filesize, uint8_t *filename, uint16_t filename_length); +int tox_new_file_sender(Tox *tox, int friendnumber, uint64_t filesize, uint8_t *filename, uint16_t filename_length); /* Send a file control request. * * send_receive is 0 if we want the control packet to target a file we are currently sending, * 1 if it targets a file we are currently receiving. * - * return 1 on success - * return 0 on failure + * return 0 on success + * return -1 on failure */ -int tox_file_sendcontrol(Tox *tox, int friendnumber, uint8_t send_receive, uint8_t filenumber, uint8_t message_id, +int tox_file_send_control(Tox *tox, int friendnumber, uint8_t send_receive, uint8_t filenumber, uint8_t message_id, uint8_t *data, uint16_t length); /* Send file data. * - * return 1 on success - * return 0 on failure + * return 0 on success + * return -1 on failure */ -int tox_file_senddata(Tox *tox, int friendnumber, uint8_t filenumber, uint8_t *data, uint16_t length); +int tox_file_send_data(Tox *tox, int friendnumber, uint8_t filenumber, uint8_t *data, uint16_t length); -/* Returns the recommended/maximum size of the filedata you send with tox_file_senddata() +/* Returns the recommended/maximum size of the filedata you send with tox_file_send_data() * * return size on success - * return 0 on failure (currently will never return 0) + * return -1 on failure (currently will never return -1) */ -int tox_filedata_size(Tox *tox, int friendnumber); +int tox_file_data_size(Tox *tox, int friendnumber); /* Give the number of bytes left to be sent/received. * @@ -507,7 +546,7 @@ int tox_filedata_size(Tox *tox, int friendnumber); * return number of bytes remaining to be sent/received on success * return 0 on failure */ -uint64_t tox_file_dataremaining(Tox *tox, int friendnumber, uint8_t filenumber, uint8_t send_receive); +uint64_t tox_file_data_remaining(Tox *tox, int friendnumber, uint8_t filenumber, uint8_t send_receive); /***************END OF FILE SENDING FUNCTIONS******************/ @@ -567,10 +606,11 @@ void tox_do(Tox *tox); * Prepares the data required to call tox_wait_execute() asynchronously * * data[] is reserved and kept by the caller - * len is in/out: in = reserved data[], out = required data[] + * *lenptr is in/out: in = reserved data[], out = required data[] * - * returns 1 on success - * returns 0 on failure (length is insufficient) + * returns 1 on success + * returns 0 if *lenptr is insufficient + * returns -1 if lenptr is NULL * * * tox_wait_execute(): function can be called asynchronously