diff options
Diffstat (limited to 'libs/libtox/src/toxcore')
47 files changed, 34749 insertions, 0 deletions
diff --git a/libs/libtox/src/toxcore/DHT.c b/libs/libtox/src/toxcore/DHT.c new file mode 100644 index 0000000000..4ebe7f3344 --- /dev/null +++ b/libs/libtox/src/toxcore/DHT.c @@ -0,0 +1,2853 @@ +/* + * An implementation of the DHT as seen in docs/updates/DHT.md + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2013 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "DHT.h" + +#include "LAN_discovery.h" +#include "logger.h" +#include "network.h" +#include "ping.h" +#include "util.h" + +#include <assert.h> + +/* The timeout after which a node is discarded completely. */ +#define KILL_NODE_TIMEOUT (BAD_NODE_TIMEOUT + PING_INTERVAL) + +/* Ping interval in seconds for each random sending of a get nodes request. */ +#define GET_NODE_INTERVAL 20 + +#define MAX_PUNCHING_PORTS 48 + +/* Interval in seconds between punching attempts*/ +#define PUNCH_INTERVAL 3 + +/* Time in seconds after which punching parameters will be reset */ +#define PUNCH_RESET_TIME 40 + +#define MAX_NORMAL_PUNCHING_TRIES 5 + +#define NAT_PING_REQUEST 0 +#define NAT_PING_RESPONSE 1 + +/* Number of get node requests to send to quickly find close nodes. */ +#define MAX_BOOTSTRAP_TIMES 5 + +#define ASSOC_COUNT 2 + +/* Compares pk1 and pk2 with pk. + * + * return 0 if both are same distance. + * return 1 if pk1 is closer. + * return 2 if pk2 is closer. + */ +int id_closest(const uint8_t *pk, const uint8_t *pk1, const uint8_t *pk2) +{ + for (size_t i = 0; i < CRYPTO_PUBLIC_KEY_SIZE; ++i) { + + uint8_t distance1 = pk[i] ^ pk1[i]; + uint8_t distance2 = pk[i] ^ pk2[i]; + + if (distance1 < distance2) { + return 1; + } + + if (distance1 > distance2) { + return 2; + } + } + + return 0; +} + +/* Return index of first unequal bit number. + */ +static unsigned int bit_by_bit_cmp(const uint8_t *pk1, const uint8_t *pk2) +{ + unsigned int i; + unsigned int j = 0; + + for (i = 0; i < CRYPTO_PUBLIC_KEY_SIZE; ++i) { + if (pk1[i] == pk2[i]) { + continue; + } + + for (j = 0; j < 8; ++j) { + uint8_t mask = 1 << (7 - j); + + if ((pk1[i] & mask) != (pk2[i] & mask)) { + break; + } + } + + break; + } + + return i * 8 + j; +} + +/* Shared key generations are costly, it is therefor smart to store commonly used + * ones so that they can re used later without being computed again. + * + * If shared key is already in shared_keys, copy it to shared_key. + * else generate it into shared_key and copy it to shared_keys + */ +void get_shared_key(Shared_Keys *shared_keys, uint8_t *shared_key, const uint8_t *secret_key, const uint8_t *public_key) +{ + uint32_t num = ~0; + uint32_t curr = 0; + + for (uint32_t i = 0; i < MAX_KEYS_PER_SLOT; ++i) { + int index = public_key[30] * MAX_KEYS_PER_SLOT + i; + Shared_Key *key = &shared_keys->keys[index]; + + if (key->stored) { + if (id_equal(public_key, key->public_key)) { + memcpy(shared_key, key->shared_key, CRYPTO_SHARED_KEY_SIZE); + ++key->times_requested; + key->time_last_requested = unix_time(); + return; + } + + if (num != 0) { + if (is_timeout(key->time_last_requested, KEYS_TIMEOUT)) { + num = 0; + curr = index; + } else if (num > key->times_requested) { + num = key->times_requested; + curr = index; + } + } + } else if (num != 0) { + num = 0; + curr = index; + } + } + + encrypt_precompute(public_key, secret_key, shared_key); + + if (num != (uint32_t)~0) { + Shared_Key *key = &shared_keys->keys[curr]; + key->stored = 1; + key->times_requested = 1; + memcpy(key->public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(key->shared_key, shared_key, CRYPTO_SHARED_KEY_SIZE); + key->time_last_requested = unix_time(); + } +} + +/* Copy shared_key to encrypt/decrypt DHT packet from public_key into shared_key + * for packets that we receive. + */ +void DHT_get_shared_key_recv(DHT *dht, uint8_t *shared_key, const uint8_t *public_key) +{ + get_shared_key(&dht->shared_keys_recv, shared_key, dht->self_secret_key, public_key); +} + +/* Copy shared_key to encrypt/decrypt DHT packet from public_key into shared_key + * for packets that we send. + */ +void DHT_get_shared_key_sent(DHT *dht, uint8_t *shared_key, const uint8_t *public_key) +{ + get_shared_key(&dht->shared_keys_sent, shared_key, dht->self_secret_key, public_key); +} + +#define CRYPTO_SIZE 1 + CRYPTO_PUBLIC_KEY_SIZE * 2 + CRYPTO_NONCE_SIZE + +/* Create a request to peer. + * send_public_key and send_secret_key are the pub/secret keys of the sender. + * recv_public_key is public key of receiver. + * packet must be an array of MAX_CRYPTO_REQUEST_SIZE big. + * Data represents the data we send with the request with length being the length of the data. + * request_id is the id of the request (32 = friend request, 254 = ping request). + * + * return -1 on failure. + * return the length of the created packet on success. + */ +int create_request(const uint8_t *send_public_key, const uint8_t *send_secret_key, uint8_t *packet, + const uint8_t *recv_public_key, const uint8_t *data, uint32_t length, uint8_t request_id) +{ + if (!send_public_key || !packet || !recv_public_key || !data) { + return -1; + } + + if (MAX_CRYPTO_REQUEST_SIZE < length + CRYPTO_SIZE + 1 + CRYPTO_MAC_SIZE) { + return -1; + } + + uint8_t *nonce = packet + 1 + CRYPTO_PUBLIC_KEY_SIZE * 2; + random_nonce(nonce); + uint8_t temp[MAX_CRYPTO_REQUEST_SIZE]; + memcpy(temp + 1, data, length); + temp[0] = request_id; + int len = encrypt_data(recv_public_key, send_secret_key, nonce, temp, length + 1, + CRYPTO_SIZE + packet); + + if (len == -1) { + crypto_memzero(temp, MAX_CRYPTO_REQUEST_SIZE); + return -1; + } + + packet[0] = NET_PACKET_CRYPTO; + memcpy(packet + 1, recv_public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, send_public_key, CRYPTO_PUBLIC_KEY_SIZE); + + crypto_memzero(temp, MAX_CRYPTO_REQUEST_SIZE); + return len + CRYPTO_SIZE; +} + +/* Puts the senders public key in the request in public_key, the data from the request + * in data if a friend or ping request was sent to us and returns the length of the data. + * packet is the request packet and length is its length. + * + * return -1 if not valid request. + */ +int handle_request(const uint8_t *self_public_key, const uint8_t *self_secret_key, uint8_t *public_key, uint8_t *data, + uint8_t *request_id, const uint8_t *packet, uint16_t length) +{ + if (!self_public_key || !public_key || !data || !request_id || !packet) { + return -1; + } + + if (length <= CRYPTO_SIZE + CRYPTO_MAC_SIZE || length > MAX_CRYPTO_REQUEST_SIZE) { + return -1; + } + + if (!id_equal(packet + 1, self_public_key)) { + return -1; + } + + memcpy(public_key, packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, CRYPTO_PUBLIC_KEY_SIZE); + const uint8_t *nonce = packet + 1 + CRYPTO_PUBLIC_KEY_SIZE * 2; + uint8_t temp[MAX_CRYPTO_REQUEST_SIZE]; + int len1 = decrypt_data(public_key, self_secret_key, nonce, + packet + CRYPTO_SIZE, length - CRYPTO_SIZE, temp); + + if (len1 == -1 || len1 == 0) { + crypto_memzero(temp, MAX_CRYPTO_REQUEST_SIZE); + return -1; + } + + request_id[0] = temp[0]; + --len1; + memcpy(data, temp + 1, len1); + crypto_memzero(temp, MAX_CRYPTO_REQUEST_SIZE); + return len1; +} + +#define PACKED_NODE_SIZE_IP4 (1 + SIZE_IP4 + sizeof(uint16_t) + CRYPTO_PUBLIC_KEY_SIZE) +#define PACKED_NODE_SIZE_IP6 (1 + SIZE_IP6 + sizeof(uint16_t) + CRYPTO_PUBLIC_KEY_SIZE) + +/* Return packet size of packed node with ip_family on success. + * Return -1 on failure. + */ +int packed_node_size(uint8_t ip_family) +{ + switch (ip_family) { + case TOX_AF_INET: + case TCP_INET: + return PACKED_NODE_SIZE_IP4; + + case TOX_AF_INET6: + case TCP_INET6: + return PACKED_NODE_SIZE_IP6; + + default: + return -1; + } +} + + +/* Packs an IP_Port structure into data of max size length. + * + * Returns size of packed IP_Port data on success + * Return -1 on failure. + */ +static int pack_ip_port(uint8_t *data, uint16_t length, const IP_Port *ip_port) +{ + if (data == NULL) { + return -1; + } + + bool is_ipv4; + uint8_t net_family; + + if (ip_port->ip.family == TOX_AF_INET) { + // TODO(irungentoo): use functions to convert endianness + is_ipv4 = true; + net_family = TOX_AF_INET; + } else if (ip_port->ip.family == TCP_INET) { + is_ipv4 = true; + net_family = TOX_TCP_INET; + } else if (ip_port->ip.family == TOX_AF_INET6) { + is_ipv4 = false; + net_family = TOX_AF_INET6; + } else if (ip_port->ip.family == TCP_INET6) { + is_ipv4 = false; + net_family = TOX_TCP_INET6; + } else { + return -1; + } + + if (is_ipv4) { + uint32_t size = 1 + SIZE_IP4 + sizeof(uint16_t); + + if (size > length) { + return -1; + } + + data[0] = net_family; + memcpy(data + 1, &ip_port->ip.ip4, SIZE_IP4); + memcpy(data + 1 + SIZE_IP4, &ip_port->port, sizeof(uint16_t)); + return size; + } else { + uint32_t size = 1 + SIZE_IP6 + sizeof(uint16_t); + + if (size > length) { + return -1; + } + + data[0] = net_family; + memcpy(data + 1, &ip_port->ip.ip6, SIZE_IP6); + memcpy(data + 1 + SIZE_IP6, &ip_port->port, sizeof(uint16_t)); + return size; + } +} + +static int DHT_create_packet(const uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE], + const uint8_t *shared_key, const uint8_t type, uint8_t *plain, size_t plain_length, uint8_t *packet) +{ + VLA(uint8_t, encrypted, plain_length + CRYPTO_MAC_SIZE); + uint8_t nonce[CRYPTO_NONCE_SIZE]; + + random_nonce(nonce); + + int encrypted_length = encrypt_data_symmetric(shared_key, nonce, plain, plain_length, encrypted); + + if (encrypted_length == -1) { + return -1; + } + + packet[0] = type; + memcpy(packet + 1, public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, nonce, CRYPTO_NONCE_SIZE); + memcpy(packet + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE, encrypted, encrypted_length); + + return 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + encrypted_length; +} + +/* Unpack IP_Port structure from data of max size length into ip_port. + * + * Return size of unpacked ip_port on success. + * Return -1 on failure. + */ +static int unpack_ip_port(IP_Port *ip_port, const uint8_t *data, uint16_t length, uint8_t tcp_enabled) +{ + if (data == NULL) { + return -1; + } + + bool is_ipv4; + uint8_t host_family; + + if (data[0] == TOX_AF_INET) { + is_ipv4 = true; + host_family = TOX_AF_INET; + } else if (data[0] == TOX_TCP_INET) { + if (!tcp_enabled) { + return -1; + } + + is_ipv4 = true; + host_family = TCP_INET; + } else if (data[0] == TOX_AF_INET6) { + is_ipv4 = false; + host_family = TOX_AF_INET6; + } else if (data[0] == TOX_TCP_INET6) { + if (!tcp_enabled) { + return -1; + } + + is_ipv4 = false; + host_family = TCP_INET6; + } else { + return -1; + } + + if (is_ipv4) { + uint32_t size = 1 + SIZE_IP4 + sizeof(uint16_t); + + if (size > length) { + return -1; + } + + ip_port->ip.family = host_family; + memcpy(&ip_port->ip.ip4, data + 1, SIZE_IP4); + memcpy(&ip_port->port, data + 1 + SIZE_IP4, sizeof(uint16_t)); + return size; + } else { + uint32_t size = 1 + SIZE_IP6 + sizeof(uint16_t); + + if (size > length) { + return -1; + } + + ip_port->ip.family = host_family; + memcpy(&ip_port->ip.ip6, data + 1, SIZE_IP6); + memcpy(&ip_port->port, data + 1 + SIZE_IP6, sizeof(uint16_t)); + return size; + } +} + +/* Pack number of nodes into data of maxlength length. + * + * return length of packed nodes on success. + * return -1 on failure. + */ +int pack_nodes(uint8_t *data, uint16_t length, const Node_format *nodes, uint16_t number) +{ + uint32_t packed_length = 0; + + for (uint32_t i = 0; i < number && packed_length < length; ++i) { + int ipp_size = pack_ip_port(data + packed_length, length - packed_length, &nodes[i].ip_port); + + if (ipp_size == -1) { + return -1; + } + + packed_length += ipp_size; + + if (packed_length + CRYPTO_PUBLIC_KEY_SIZE > length) { + return -1; + } + + memcpy(data + packed_length, nodes[i].public_key, CRYPTO_PUBLIC_KEY_SIZE); + packed_length += CRYPTO_PUBLIC_KEY_SIZE; + + uint32_t increment = ipp_size + CRYPTO_PUBLIC_KEY_SIZE; + assert(increment == PACKED_NODE_SIZE_IP4 || increment == PACKED_NODE_SIZE_IP6); + } + + return packed_length; +} + +/* Unpack data of length into nodes of size max_num_nodes. + * Put the length of the data processed in processed_data_len. + * tcp_enabled sets if TCP nodes are expected (true) or not (false). + * + * return number of unpacked nodes on success. + * return -1 on failure. + */ +int unpack_nodes(Node_format *nodes, uint16_t max_num_nodes, uint16_t *processed_data_len, const uint8_t *data, + uint16_t length, uint8_t tcp_enabled) +{ + uint32_t num = 0, len_processed = 0; + + while (num < max_num_nodes && len_processed < length) { + int ipp_size = unpack_ip_port(&nodes[num].ip_port, data + len_processed, length - len_processed, tcp_enabled); + + if (ipp_size == -1) { + return -1; + } + + len_processed += ipp_size; + + if (len_processed + CRYPTO_PUBLIC_KEY_SIZE > length) { + return -1; + } + + memcpy(nodes[num].public_key, data + len_processed, CRYPTO_PUBLIC_KEY_SIZE); + len_processed += CRYPTO_PUBLIC_KEY_SIZE; + ++num; + + uint32_t increment = ipp_size + CRYPTO_PUBLIC_KEY_SIZE; + assert(increment == PACKED_NODE_SIZE_IP4 || increment == PACKED_NODE_SIZE_IP6); + } + + if (processed_data_len) { + *processed_data_len = len_processed; + } + + return num; +} + +/* Find index of ##type with public_key equal to pk. + * + * return index or UINT32_MAX if not found. + */ +#define INDEX_OF_PK \ + for (uint32_t i = 0; i < size; i++) { \ + if (id_equal(array[i].public_key, pk)) { \ + return i; \ + } \ + } \ + \ + return UINT32_MAX; + +static uint32_t index_of_client_pk(const Client_data *array, uint32_t size, const uint8_t *pk) +{ + INDEX_OF_PK +} + +static uint32_t index_of_friend_pk(const DHT_Friend *array, uint32_t size, const uint8_t *pk) +{ + INDEX_OF_PK +} + +static uint32_t index_of_node_pk(const Node_format *array, uint32_t size, const uint8_t *pk) +{ + INDEX_OF_PK +} + +/* Find index of Client_data with ip_port equal to param ip_port. + * + * return index or UINT32_MAX if not found. + */ +static uint32_t index_of_client_ip_port(const Client_data *array, uint32_t size, const IP_Port *ip_port) +{ + for (uint32_t i = 0; i < size; ++i) { + if (ip_port->ip.family == TOX_AF_INET && ipport_equal(&array[i].assoc4.ip_port, ip_port) || + ip_port->ip.family == TOX_AF_INET6 && ipport_equal(&array[i].assoc6.ip_port, ip_port)) { + return i; + } + } + + return UINT32_MAX; +} + +/* Update ip_port of client if it's needed. + */ +static void update_client(Logger *log, int index, Client_data *client, IP_Port ip_port) +{ + IPPTsPng *assoc; + int ip_version; + + if (ip_port.ip.family == TOX_AF_INET) { + assoc = &client->assoc4; + ip_version = 4; + } else if (ip_port.ip.family == TOX_AF_INET6) { + assoc = &client->assoc6; + ip_version = 6; + } else { + return; + } + + if (!ipport_equal(&assoc->ip_port, &ip_port)) { + char ip_str[IP_NTOA_LEN]; + LOGGER_TRACE(log, "coipil[%u]: switching ipv%d from %s:%u to %s:%u", + index, ip_version, + ip_ntoa(&assoc->ip_port.ip, ip_str, sizeof(ip_str)), + net_ntohs(assoc->ip_port.port), + ip_ntoa(&ip_port.ip, ip_str, sizeof(ip_str)), + net_ntohs(ip_port.port)); + } + + if (LAN_ip(assoc->ip_port.ip) != 0 && LAN_ip(ip_port.ip) == 0) { + return; + } + + assoc->ip_port = ip_port; + assoc->timestamp = unix_time(); +} + +/* Check if client with public_key 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. + * TODO(irungentoo): Maybe optimize this. + * + * return True(1) or False(0) + */ +static int client_or_ip_port_in_list(Logger *log, Client_data *list, uint16_t length, const uint8_t *public_key, + IP_Port ip_port) +{ + uint64_t temp_time = unix_time(); + uint32_t index = index_of_client_pk(list, length, public_key); + + /* if public_key is in list, find it and maybe overwrite ip_port */ + if (index != UINT32_MAX) { + update_client(log, index, &list[index], ip_port); + return 1; + } + + /* public_key not in list yet: see if we can find an identical ip_port, in + * that case we kill the old public_key by overwriting it with the new one + * TODO(irungentoo): maybe we SHOULDN'T do that if that public_key is in a friend_list + * and the one who is the actual friend's public_key/address set? + * MAYBE: check the other address, if valid, don't nuke? */ + index = index_of_client_ip_port(list, length, &ip_port); + + if (index == UINT32_MAX) { + return 0; + } + + IPPTsPng *assoc; + int ip_version; + + if (ip_port.ip.family == TOX_AF_INET) { + assoc = &list[index].assoc4; + ip_version = 4; + } else { + assoc = &list[index].assoc6; + ip_version = 6; + } + + /* Initialize client timestamp. */ + assoc->timestamp = temp_time; + memcpy(list[index].public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); + + LOGGER_DEBUG(log, "coipil[%u]: switching public_key (ipv%d)", index, ip_version); + + /* kill the other address, if it was set */ + memset(assoc, 0, sizeof(IPPTsPng)); + return 1; +} + +/* Add node to the node list making sure only the nodes closest to cmp_pk are in the list. + */ +bool add_to_list(Node_format *nodes_list, unsigned int length, const uint8_t *pk, IP_Port ip_port, + const uint8_t *cmp_pk) +{ + uint8_t pk_bak[CRYPTO_PUBLIC_KEY_SIZE]; + IP_Port ip_port_bak; + + for (size_t i = 0; i < length; ++i) { + if (id_closest(cmp_pk, nodes_list[i].public_key, pk) == 2) { + memcpy(pk_bak, nodes_list[i].public_key, CRYPTO_PUBLIC_KEY_SIZE); + ip_port_bak = nodes_list[i].ip_port; + memcpy(nodes_list[i].public_key, pk, CRYPTO_PUBLIC_KEY_SIZE); + nodes_list[i].ip_port = ip_port; + + if (i != (length - 1)) { + add_to_list(nodes_list, length, pk_bak, ip_port_bak, cmp_pk); + } + + return 1; + } + } + + return 0; +} + +/* TODO(irungentoo): change this to 7 when done*/ +#define HARDENING_ALL_OK 2 +/* return 0 if not. + * return 1 if route request are ok + * return 2 if it responds to send node packets correctly + * return 4 if it can test other nodes correctly + * return HARDENING_ALL_OK if all ok. + */ +static uint8_t hardening_correct(const Hardening *h) +{ + return h->routes_requests_ok + (h->send_nodes_ok << 1) + (h->testing_requests << 2); +} +/* + * helper for get_close_nodes(). argument list is a monster :D + */ +static void get_close_nodes_inner(const uint8_t *public_key, Node_format *nodes_list, + Family sa_family, const Client_data *client_list, uint32_t client_list_length, + uint32_t *num_nodes_ptr, uint8_t is_LAN, uint8_t want_good) +{ + if ((sa_family != TOX_AF_INET) && (sa_family != TOX_AF_INET6) && (sa_family != 0)) { + return; + } + + uint32_t num_nodes = *num_nodes_ptr; + + for (uint32_t i = 0; i < client_list_length; i++) { + const Client_data *client = &client_list[i]; + + /* node already in list? */ + if (index_of_node_pk(nodes_list, MAX_SENT_NODES, client->public_key) != UINT32_MAX) { + continue; + } + + const IPPTsPng *ipptp = NULL; + + if (sa_family == TOX_AF_INET) { + ipptp = &client->assoc4; + } else if (sa_family == TOX_AF_INET6) { + ipptp = &client->assoc6; + } else if (client->assoc4.timestamp >= client->assoc6.timestamp) { + ipptp = &client->assoc4; + } else { + ipptp = &client->assoc6; + } + + /* node not in a good condition? */ + if (is_timeout(ipptp->timestamp, BAD_NODE_TIMEOUT)) { + continue; + } + + /* don't send LAN ips to non LAN peers */ + if (LAN_ip(ipptp->ip_port.ip) == 0 && !is_LAN) { + continue; + } + + if (LAN_ip(ipptp->ip_port.ip) != 0 && want_good && hardening_correct(&ipptp->hardening) != HARDENING_ALL_OK + && !id_equal(public_key, client->public_key)) { + continue; + } + + if (num_nodes < MAX_SENT_NODES) { + memcpy(nodes_list[num_nodes].public_key, client->public_key, CRYPTO_PUBLIC_KEY_SIZE); + nodes_list[num_nodes].ip_port = ipptp->ip_port; + num_nodes++; + } else { + add_to_list(nodes_list, MAX_SENT_NODES, client->public_key, ipptp->ip_port, public_key); + } + } + + *num_nodes_ptr = num_nodes; +} + +/* Find MAX_SENT_NODES nodes closest to the public_key for the send nodes request: + * put them in the nodes_list and return how many were found. + * + * TODO(irungentoo): For the love of based <your favorite deity, in doubt use + * "love"> make this function cleaner and much more efficient. + * + * want_good : do we want only good nodes as checked with the hardening returned or not? + */ +static int get_somewhat_close_nodes(const DHT *dht, const uint8_t *public_key, Node_format *nodes_list, + Family sa_family, uint8_t is_LAN, uint8_t want_good) +{ + uint32_t num_nodes = 0; + get_close_nodes_inner(public_key, nodes_list, sa_family, + dht->close_clientlist, LCLIENT_LIST, &num_nodes, is_LAN, 0); + + /* TODO(irungentoo): uncomment this when hardening is added to close friend clients */ +#if 0 + + for (uint32_t i = 0; i < dht->num_friends; ++i) { + get_close_nodes_inner(dht, public_key, nodes_list, sa_family, + dht->friends_list[i].client_list, MAX_FRIEND_CLIENTS, + &num_nodes, is_LAN, want_good); + } + +#endif + + for (uint32_t i = 0; i < dht->num_friends; ++i) { + get_close_nodes_inner(public_key, nodes_list, sa_family, + dht->friends_list[i].client_list, MAX_FRIEND_CLIENTS, + &num_nodes, is_LAN, 0); + } + + return num_nodes; +} + +int get_close_nodes(const DHT *dht, const uint8_t *public_key, Node_format *nodes_list, Family sa_family, + uint8_t is_LAN, uint8_t want_good) +{ + memset(nodes_list, 0, MAX_SENT_NODES * sizeof(Node_format)); + return get_somewhat_close_nodes(dht, public_key, nodes_list, sa_family, is_LAN, want_good); +} + +typedef struct { + const uint8_t *base_public_key; + Client_data entry; +} DHT_Cmp_data; + +static int cmp_dht_entry(const void *a, const void *b) +{ + DHT_Cmp_data cmp1, cmp2; + memcpy(&cmp1, a, sizeof(DHT_Cmp_data)); + memcpy(&cmp2, b, sizeof(DHT_Cmp_data)); + Client_data entry1 = cmp1.entry; + Client_data entry2 = cmp2.entry; + const uint8_t *cmp_public_key = cmp1.base_public_key; + +#define ASSOC_TIMEOUT(assoc) is_timeout((assoc).timestamp, BAD_NODE_TIMEOUT) + + bool t1 = ASSOC_TIMEOUT(entry1.assoc4) && ASSOC_TIMEOUT(entry1.assoc6); + bool t2 = ASSOC_TIMEOUT(entry2.assoc4) && ASSOC_TIMEOUT(entry2.assoc6); + + if (t1 && t2) { + return 0; + } + + if (t1) { + return -1; + } + + if (t2) { + return 1; + } + +#define INCORRECT_HARDENING(assoc) hardening_correct(&(assoc).hardening) != HARDENING_ALL_OK + + t1 = INCORRECT_HARDENING(entry1.assoc4) && INCORRECT_HARDENING(entry1.assoc6); + t2 = INCORRECT_HARDENING(entry2.assoc4) && INCORRECT_HARDENING(entry2.assoc6); + + if (t1 && !t2) { + return -1; + } + + if (!t1 && t2) { + return 1; + } + + int close = id_closest(cmp_public_key, entry1.public_key, entry2.public_key); + + if (close == 1) { + return 1; + } + + if (close == 2) { + return -1; + } + + return 0; +} + +/* Is it ok to store node with public_key in client. + * + * return 0 if node can't be stored. + * return 1 if it can. + */ +static unsigned int store_node_ok(const Client_data *client, const uint8_t *public_key, const uint8_t *comp_public_key) +{ + return is_timeout(client->assoc4.timestamp, BAD_NODE_TIMEOUT) && + is_timeout(client->assoc6.timestamp, BAD_NODE_TIMEOUT) || + id_closest(comp_public_key, client->public_key, public_key) == 2; +} + +static void sort_client_list(Client_data *list, unsigned int length, const uint8_t *comp_public_key) +{ + // Pass comp_public_key to qsort with each Client_data entry, so the + // comparison function can use it as the base of comparison. + VLA(DHT_Cmp_data, cmp_list, length); + + for (uint32_t i = 0; i < length; i++) { + cmp_list[i].base_public_key = comp_public_key; + cmp_list[i].entry = list[i]; + } + + qsort(cmp_list, length, sizeof(DHT_Cmp_data), cmp_dht_entry); + + for (uint32_t i = 0; i < length; i++) { + list[i] = cmp_list[i].entry; + } +} + +static void update_client_with_reset(Client_data *client, const IP_Port *ip_port) +{ + IPPTsPng *ipptp_write = NULL; + IPPTsPng *ipptp_clear = NULL; + + if (ip_port->ip.family == TOX_AF_INET) { + ipptp_write = &client->assoc4; + ipptp_clear = &client->assoc6; + } else { + ipptp_write = &client->assoc6; + ipptp_clear = &client->assoc4; + } + + 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)); +} + +/* Replace a first bad (or empty) node with this one + * or replace a possibly bad node (tests failed or not done yet) + * that is further than any other in the list + * from the comp_public_key + * or replace a good node that is further + * than any other in the list from the comp_public_key + * and further than public_key. + * + * Do not replace any node if the list has no bad or possibly bad nodes + * and all nodes in the list are closer to comp_public_key + * than public_key. + * + * returns True(1) when the item was stored, False(0) otherwise */ +static int replace_all(Client_data *list, + uint16_t length, + const uint8_t *public_key, + IP_Port ip_port, + const uint8_t *comp_public_key) +{ + if ((ip_port.ip.family != TOX_AF_INET) && (ip_port.ip.family != TOX_AF_INET6)) { + return 0; + } + + if (!store_node_ok(&list[1], public_key, comp_public_key) && + !store_node_ok(&list[0], public_key, comp_public_key)) { + return 0; + } + + sort_client_list(list, length, comp_public_key); + + Client_data *client = &list[0]; + id_copy(client->public_key, public_key); + + update_client_with_reset(client, &ip_port); + return 1; +} + +/* Add node to close list. + * + * simulate is set to 1 if we want to check if a node can be added to the list without adding it. + * + * return -1 on failure. + * return 0 on success. + */ +static int add_to_close(DHT *dht, const uint8_t *public_key, IP_Port ip_port, bool simulate) +{ + unsigned int index = bit_by_bit_cmp(public_key, dht->self_public_key); + + if (index >= LCLIENT_LENGTH) { + index = LCLIENT_LENGTH - 1; + } + + for (uint32_t i = 0; i < LCLIENT_NODES; ++i) { + /* TODO(iphydf): write bounds checking test to catch the case that + * index is left as >= LCLIENT_LENGTH */ + Client_data *client = &dht->close_clientlist[(index * LCLIENT_NODES) + i]; + + if (!is_timeout(client->assoc4.timestamp, BAD_NODE_TIMEOUT) || + !is_timeout(client->assoc6.timestamp, BAD_NODE_TIMEOUT)) { + continue; + } + + if (simulate) { + return 0; + } + + id_copy(client->public_key, public_key); + update_client_with_reset(client, &ip_port); + return 0; + } + + return -1; +} + +/* Return 1 if node can be added to close list, 0 if it can't. + */ +bool node_addable_to_close_list(DHT *dht, const uint8_t *public_key, IP_Port ip_port) +{ + return add_to_close(dht, public_key, ip_port, 1) == 0; +} + +static bool is_pk_in_client_list(Client_data *list, unsigned int client_list_length, const uint8_t *public_key, + IP_Port ip_port) +{ + uint32_t index = index_of_client_pk(list, client_list_length, public_key); + + if (index == UINT32_MAX) { + return 0; + } + + const IPPTsPng *assoc = ip_port.ip.family == TOX_AF_INET ? + &list[index].assoc4 : + &list[index].assoc6; + + return !is_timeout(assoc->timestamp, BAD_NODE_TIMEOUT); +} + +static bool is_pk_in_close_list(DHT *dht, const uint8_t *public_key, IP_Port ip_port) +{ + unsigned int index = bit_by_bit_cmp(public_key, dht->self_public_key); + + if (index >= LCLIENT_LENGTH) { + index = LCLIENT_LENGTH - 1; + } + + return is_pk_in_client_list(dht->close_clientlist + index * LCLIENT_NODES, LCLIENT_NODES, public_key, ip_port); +} + +/* Check if the node obtained with a get_nodes with public_key should be pinged. + * NOTE: for best results call it after addto_lists; + * + * return 0 if the node should not be pinged. + * return 1 if it should. + */ +static unsigned int ping_node_from_getnodes_ok(DHT *dht, const uint8_t *public_key, IP_Port ip_port) +{ + bool ret = 0; + + if (add_to_close(dht, public_key, ip_port, 1) == 0) { + ret = 1; + } + + unsigned int *num = &dht->num_to_bootstrap; + uint32_t index = index_of_node_pk(dht->to_bootstrap, *num, public_key); + bool in_close_list = is_pk_in_close_list(dht, public_key, ip_port); + + if (ret && index == UINT32_MAX && !in_close_list) { + if (*num < MAX_CLOSE_TO_BOOTSTRAP_NODES) { + memcpy(dht->to_bootstrap[*num].public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); + dht->to_bootstrap[*num].ip_port = ip_port; + ++*num; + } else { + // TODO(irungentoo): ipv6 vs v4 + add_to_list(dht->to_bootstrap, MAX_CLOSE_TO_BOOTSTRAP_NODES, public_key, ip_port, dht->self_public_key); + } + } + + for (uint32_t i = 0; i < dht->num_friends; ++i) { + bool store_ok = 0; + + DHT_Friend *dht_friend = &dht->friends_list[i]; + + if (store_node_ok(&dht_friend->client_list[1], public_key, dht_friend->public_key)) { + store_ok = 1; + } + + if (store_node_ok(&dht_friend->client_list[0], public_key, dht_friend->public_key)) { + store_ok = 1; + } + + unsigned int *friend_num = &dht_friend->num_to_bootstrap; + const uint32_t index = index_of_node_pk(dht_friend->to_bootstrap, *friend_num, public_key); + const bool pk_in_list = is_pk_in_client_list(dht_friend->client_list, MAX_FRIEND_CLIENTS, public_key, ip_port); + + if (store_ok && index == UINT32_MAX && !pk_in_list) { + if (*friend_num < MAX_SENT_NODES) { + Node_format *format = &dht_friend->to_bootstrap[*friend_num]; + memcpy(format->public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); + format->ip_port = ip_port; + ++*friend_num; + } else { + add_to_list(dht_friend->to_bootstrap, MAX_SENT_NODES, public_key, ip_port, dht_friend->public_key); + } + + ret = 1; + } + } + + return ret; +} + +/* Attempt to add client with ip_port and public_key to the friends client list + * and close_clientlist. + * + * returns 1+ if the item is used in any list, 0 else + */ +uint32_t addto_lists(DHT *dht, IP_Port ip_port, const uint8_t *public_key) +{ + uint32_t used = 0; + + /* convert IPv4-in-IPv6 to IPv4 */ + if ((ip_port.ip.family == TOX_AF_INET6) && IPV6_IPV4_IN_V6(ip_port.ip.ip6)) { + ip_port.ip.family = TOX_AF_INET; + ip_port.ip.ip4.uint32 = ip_port.ip.ip6.uint32[3]; + } + + /* NOTE: Current behavior if there are two clients with the same id is + * to replace the first ip by the second. + */ + const bool in_close_list = client_or_ip_port_in_list(dht->log, dht->close_clientlist, + LCLIENT_LIST, public_key, ip_port); + + /* add_to_close should be called only if !in_list (don't extract to variable) */ + if (in_close_list || add_to_close(dht, public_key, ip_port, 0)) { + used++; + } + + DHT_Friend *friend_foundip = 0; + + for (uint32_t i = 0; i < dht->num_friends; ++i) { + const bool in_list = client_or_ip_port_in_list(dht->log, dht->friends_list[i].client_list, + MAX_FRIEND_CLIENTS, public_key, ip_port); + + /* replace_all should be called only if !in_list (don't extract to variable) */ + if (in_list || replace_all(dht->friends_list[i].client_list, MAX_FRIEND_CLIENTS, public_key, + ip_port, dht->friends_list[i].public_key)) { + DHT_Friend *dht_friend = &dht->friends_list[i]; + + if (id_equal(public_key, dht_friend->public_key)) { + friend_foundip = dht_friend; + } + + used++; + } + } + + if (!friend_foundip) { + return used; + } + + for (uint32_t i = 0; i < friend_foundip->lock_count; ++i) { + if (friend_foundip->callbacks[i].ip_callback) { + friend_foundip->callbacks[i].ip_callback(friend_foundip->callbacks[i].data, + friend_foundip->callbacks[i].number, ip_port); + } + } + + return used; +} + +static bool update_client_data(Client_data *array, size_t size, IP_Port ip_port, const uint8_t *pk) +{ + uint64_t temp_time = unix_time(); + uint32_t index = index_of_client_pk(array, size, pk); + + if (index == UINT32_MAX) { + return false; + } + + Client_data *data = &array[index]; + IPPTsPng *assoc; + + if (ip_port.ip.family == TOX_AF_INET) { + assoc = &data->assoc4; + } else if (ip_port.ip.family == TOX_AF_INET6) { + assoc = &data->assoc6; + } else { + return true; + } + + assoc->ret_ip_port = ip_port; + assoc->ret_timestamp = temp_time; + return true; +} + +/* If public_key is a friend or us, update ret_ip_port + * nodepublic_key is the id of the node that sent us this info. + */ +static void returnedip_ports(DHT *dht, IP_Port ip_port, const uint8_t *public_key, const uint8_t *nodepublic_key) +{ + /* convert IPv4-in-IPv6 to IPv4 */ + if ((ip_port.ip.family == TOX_AF_INET6) && IPV6_IPV4_IN_V6(ip_port.ip.ip6)) { + ip_port.ip.family = TOX_AF_INET; + ip_port.ip.ip4.uint32 = ip_port.ip.ip6.uint32[3]; + } + + if (id_equal(public_key, dht->self_public_key)) { + update_client_data(dht->close_clientlist, LCLIENT_LIST, ip_port, nodepublic_key); + return; + } + + for (uint32_t i = 0; i < dht->num_friends; ++i) { + if (id_equal(public_key, dht->friends_list[i].public_key)) { + Client_data *client_list = dht->friends_list[i].client_list; + + if (update_client_data(client_list, MAX_FRIEND_CLIENTS, ip_port, nodepublic_key)) { + return; + } + } + } +} + +/* Send a getnodes request. + sendback_node is the node that it will send back the response to (set to NULL to disable this) */ +static int getnodes(DHT *dht, IP_Port ip_port, const uint8_t *public_key, const uint8_t *client_id, + const Node_format *sendback_node) +{ + /* Check if packet is going to be sent to ourself. */ + if (id_equal(public_key, dht->self_public_key)) { + return -1; + } + + uint8_t plain_message[sizeof(Node_format) * 2] = {0}; + + Node_format receiver; + memcpy(receiver.public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); + receiver.ip_port = ip_port; + memcpy(plain_message, &receiver, sizeof(receiver)); + + uint64_t ping_id = 0; + + if (sendback_node != NULL) { + memcpy(plain_message + sizeof(receiver), sendback_node, sizeof(Node_format)); + ping_id = ping_array_add(&dht->dht_harden_ping_array, plain_message, sizeof(plain_message)); + } else { + ping_id = ping_array_add(&dht->dht_ping_array, plain_message, sizeof(receiver)); + } + + if (ping_id == 0) { + return -1; + } + + uint8_t plain[CRYPTO_PUBLIC_KEY_SIZE + sizeof(ping_id)]; + uint8_t data[1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + sizeof(plain) + CRYPTO_MAC_SIZE]; + + memcpy(plain, client_id, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(plain + CRYPTO_PUBLIC_KEY_SIZE, &ping_id, sizeof(ping_id)); + + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + DHT_get_shared_key_sent(dht, shared_key, public_key); + + int len = DHT_create_packet(dht->self_public_key, shared_key, NET_PACKET_GET_NODES, + plain, sizeof(plain), data); + + if (len != sizeof(data)) { + return -1; + } + + return sendpacket(dht->net, ip_port, data, len); +} + +/* Send a send nodes response: message for IPv6 nodes */ +static int sendnodes_ipv6(const DHT *dht, IP_Port ip_port, const uint8_t *public_key, const uint8_t *client_id, + const uint8_t *sendback_data, uint16_t length, const uint8_t *shared_encryption_key) +{ + /* Check if packet is going to be sent to ourself. */ + if (id_equal(public_key, dht->self_public_key)) { + return -1; + } + + if (length != sizeof(uint64_t)) { + return -1; + } + + size_t Node_format_size = sizeof(Node_format); + + Node_format nodes_list[MAX_SENT_NODES]; + uint32_t num_nodes = get_close_nodes(dht, client_id, nodes_list, 0, LAN_ip(ip_port.ip) == 0, 1); + + VLA(uint8_t, plain, 1 + Node_format_size * MAX_SENT_NODES + length); + + int nodes_length = 0; + + if (num_nodes) { + nodes_length = pack_nodes(plain + 1, Node_format_size * MAX_SENT_NODES, nodes_list, num_nodes); + + if (nodes_length <= 0) { + return -1; + } + } + + plain[0] = num_nodes; + memcpy(plain + 1 + nodes_length, sendback_data, length); + + const uint32_t crypto_size = 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + CRYPTO_MAC_SIZE; + VLA(uint8_t, data, 1 + nodes_length + length + crypto_size); + + int len = DHT_create_packet(dht->self_public_key, shared_encryption_key, NET_PACKET_SEND_NODES_IPV6, + plain, 1 + nodes_length + length, data); + + if (len != SIZEOF_VLA(data)) { + return -1; + } + + return sendpacket(dht->net, ip_port, data, len); +} + +#define CRYPTO_NODE_SIZE (CRYPTO_PUBLIC_KEY_SIZE + sizeof(uint64_t)) + +static int handle_getnodes(void *object, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) +{ + if (length != (CRYPTO_SIZE + CRYPTO_MAC_SIZE + sizeof(uint64_t))) { + return 1; + } + + DHT *dht = (DHT *)object; + + /* Check if packet is from ourself. */ + if (id_equal(packet + 1, dht->self_public_key)) { + return 1; + } + + uint8_t plain[CRYPTO_NODE_SIZE]; + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + + DHT_get_shared_key_recv(dht, shared_key, packet + 1); + int len = decrypt_data_symmetric(shared_key, + packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, + packet + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE, + CRYPTO_NODE_SIZE + CRYPTO_MAC_SIZE, + plain); + + if (len != CRYPTO_NODE_SIZE) { + return 1; + } + + sendnodes_ipv6(dht, source, packet + 1, plain, plain + CRYPTO_PUBLIC_KEY_SIZE, sizeof(uint64_t), shared_key); + + add_to_ping(dht->ping, packet + 1, source); + + return 0; +} +/* return 0 if no + return 1 if yes */ +static uint8_t sent_getnode_to_node(DHT *dht, const uint8_t *public_key, IP_Port node_ip_port, uint64_t ping_id, + Node_format *sendback_node) +{ + uint8_t data[sizeof(Node_format) * 2]; + + if (ping_array_check(data, sizeof(data), &dht->dht_ping_array, ping_id) == sizeof(Node_format)) { + memset(sendback_node, 0, sizeof(Node_format)); + } else if (ping_array_check(data, sizeof(data), &dht->dht_harden_ping_array, ping_id) == sizeof(data)) { + memcpy(sendback_node, data + sizeof(Node_format), sizeof(Node_format)); + } else { + return 0; + } + + Node_format test; + memcpy(&test, data, sizeof(Node_format)); + + if (!ipport_equal(&test.ip_port, &node_ip_port) || !id_equal(test.public_key, public_key)) { + return 0; + } + + return 1; +} + +/* Function is needed in following functions. */ +static int send_hardening_getnode_res(const DHT *dht, const Node_format *sendto, const uint8_t *queried_client_id, + const uint8_t *nodes_data, uint16_t nodes_data_length); + +static int handle_sendnodes_core(void *object, IP_Port source, const uint8_t *packet, uint16_t length, + Node_format *plain_nodes, uint16_t size_plain_nodes, uint32_t *num_nodes_out) +{ + DHT *dht = (DHT *)object; + uint32_t cid_size = 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + 1 + sizeof(uint64_t) + CRYPTO_MAC_SIZE; + + if (length < cid_size) { /* too short */ + return 1; + } + + uint32_t data_size = length - cid_size; + + if (data_size == 0) { + return 1; + } + + if (data_size > sizeof(Node_format) * MAX_SENT_NODES) { /* invalid length */ + return 1; + } + + VLA(uint8_t, plain, 1 + data_size + sizeof(uint64_t)); + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + DHT_get_shared_key_sent(dht, shared_key, packet + 1); + int len = decrypt_data_symmetric( + shared_key, + packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, + packet + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE, + 1 + data_size + sizeof(uint64_t) + CRYPTO_MAC_SIZE, + plain); + + if ((unsigned int)len != SIZEOF_VLA(plain)) { + return 1; + } + + if (plain[0] > size_plain_nodes) { + return 1; + } + + Node_format sendback_node; + + uint64_t ping_id; + memcpy(&ping_id, plain + 1 + data_size, sizeof(ping_id)); + + if (!sent_getnode_to_node(dht, packet + 1, source, ping_id, &sendback_node)) { + return 1; + } + + uint16_t length_nodes = 0; + int num_nodes = unpack_nodes(plain_nodes, plain[0], &length_nodes, plain + 1, data_size, 0); + + if (length_nodes != data_size) { + return 1; + } + + if (num_nodes != plain[0]) { + return 1; + } + + if (num_nodes < 0) { + return 1; + } + + /* store the address the *request* was sent to */ + addto_lists(dht, source, packet + 1); + + *num_nodes_out = num_nodes; + + send_hardening_getnode_res(dht, &sendback_node, packet + 1, plain + 1, data_size); + return 0; +} + +static int handle_sendnodes_ipv6(void *object, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) +{ + DHT *dht = (DHT *)object; + Node_format plain_nodes[MAX_SENT_NODES]; + uint32_t num_nodes; + + if (handle_sendnodes_core(object, source, packet, length, plain_nodes, MAX_SENT_NODES, &num_nodes)) { + return 1; + } + + if (num_nodes == 0) { + return 0; + } + + for (uint32_t i = 0; i < num_nodes; i++) { + if (ipport_isset(&plain_nodes[i].ip_port)) { + ping_node_from_getnodes_ok(dht, plain_nodes[i].public_key, plain_nodes[i].ip_port); + returnedip_ports(dht, plain_nodes[i].ip_port, plain_nodes[i].public_key, packet + 1); + } + } + + return 0; +} + +/*----------------------------------------------------------------------------------*/ +/*------------------------END of packet handling functions--------------------------*/ + +int DHT_addfriend(DHT *dht, const uint8_t *public_key, void (*ip_callback)(void *data, int32_t number, IP_Port), + void *data, int32_t number, uint16_t *lock_count) +{ + uint32_t friend_num = index_of_friend_pk(dht->friends_list, dht->num_friends, public_key); + + uint16_t lock_num; + + if (friend_num != UINT32_MAX) { /* Is friend already in DHT? */ + DHT_Friend *dht_friend = &dht->friends_list[friend_num]; + + if (dht_friend->lock_count == DHT_FRIEND_MAX_LOCKS) { + return -1; + } + + lock_num = dht_friend->lock_count; + ++dht_friend->lock_count; + dht_friend->callbacks[lock_num].ip_callback = ip_callback; + dht_friend->callbacks[lock_num].data = data; + dht_friend->callbacks[lock_num].number = number; + + if (lock_count) { + *lock_count = lock_num + 1; + } + + return 0; + } + + DHT_Friend *temp = (DHT_Friend *)realloc(dht->friends_list, sizeof(DHT_Friend) * (dht->num_friends + 1)); + + if (temp == NULL) { + return -1; + } + + dht->friends_list = temp; + DHT_Friend *dht_friend = &dht->friends_list[dht->num_friends]; + memset(dht_friend, 0, sizeof(DHT_Friend)); + memcpy(dht_friend->public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); + + dht_friend->nat.NATping_id = random_64b(); + ++dht->num_friends; + + lock_num = dht_friend->lock_count; + ++dht_friend->lock_count; + dht_friend->callbacks[lock_num].ip_callback = ip_callback; + dht_friend->callbacks[lock_num].data = data; + dht_friend->callbacks[lock_num].number = number; + + if (lock_count) { + *lock_count = lock_num + 1; + } + + dht_friend->num_to_bootstrap = get_close_nodes(dht, dht_friend->public_key, dht_friend->to_bootstrap, 0, 1, 0); + + return 0; +} + +int DHT_delfriend(DHT *dht, const uint8_t *public_key, uint16_t lock_count) +{ + uint32_t friend_num = index_of_friend_pk(dht->friends_list, dht->num_friends, public_key); + + if (friend_num == UINT32_MAX) { + return -1; + } + + DHT_Friend *dht_friend = &dht->friends_list[friend_num]; + --dht_friend->lock_count; + + if (dht_friend->lock_count && lock_count) { /* DHT friend is still in use.*/ + --lock_count; + dht_friend->callbacks[lock_count].ip_callback = NULL; + dht_friend->callbacks[lock_count].data = NULL; + dht_friend->callbacks[lock_count].number = 0; + return 0; + } + + --dht->num_friends; + + if (dht->num_friends != friend_num) { + memcpy(&dht->friends_list[friend_num], + &dht->friends_list[dht->num_friends], + sizeof(DHT_Friend)); + } + + if (dht->num_friends == 0) { + free(dht->friends_list); + dht->friends_list = NULL; + return 0; + } + + DHT_Friend *temp = (DHT_Friend *)realloc(dht->friends_list, sizeof(DHT_Friend) * (dht->num_friends)); + + if (temp == NULL) { + return -1; + } + + dht->friends_list = temp; + return 0; +} + +/* TODO(irungentoo): Optimize this. */ +int DHT_getfriendip(const DHT *dht, const uint8_t *public_key, IP_Port *ip_port) +{ + ip_reset(&ip_port->ip); + ip_port->port = 0; + + uint32_t friend_index = index_of_friend_pk(dht->friends_list, dht->num_friends, public_key); + + if (friend_index == UINT32_MAX) { + return -1; + } + + DHT_Friend *frnd = &dht->friends_list[friend_index]; + uint32_t client_index = index_of_client_pk(frnd->client_list, MAX_FRIEND_CLIENTS, public_key); + + if (client_index == -1) { + return 0; + } + + Client_data *client = &frnd->client_list[client_index]; + IPPTsPng *assocs[ASSOC_COUNT] = { &client->assoc6, &client->assoc4 }; + + for (size_t i = 0; i < ASSOC_COUNT; i++) { + IPPTsPng *assoc = assocs[i]; + + if (!is_timeout(assoc->timestamp, BAD_NODE_TIMEOUT)) { + *ip_port = assoc->ip_port; + return 1; + } + } + + return -1; +} + +/* returns number of nodes not in kill-timeout */ +static uint8_t do_ping_and_sendnode_requests(DHT *dht, uint64_t *lastgetnode, const uint8_t *public_key, + Client_data *list, uint32_t list_count, uint32_t *bootstrap_times, bool sortable) +{ + uint8_t not_kill = 0; + uint64_t temp_time = unix_time(); + + uint32_t num_nodes = 0; + VLA(Client_data *, client_list, list_count * 2); + VLA(IPPTsPng *, assoc_list, list_count * 2); + unsigned int sort = 0; + bool sort_ok = 0; + + for (uint32_t i = 0; i < list_count; i++) { + /* If node is not dead. */ + Client_data *client = &list[i]; + + IPPTsPng *assocs[ASSOC_COUNT] = { &client->assoc6, &client->assoc4 }; + + for (size_t i = 0; i < ASSOC_COUNT; i++) { + IPPTsPng *assoc = assocs[i]; + + if (!is_timeout(assoc->timestamp, KILL_NODE_TIMEOUT)) { + sort = 0; + not_kill++; + + if (is_timeout(assoc->last_pinged, PING_INTERVAL)) { + getnodes(dht, assoc->ip_port, client->public_key, public_key, NULL); + assoc->last_pinged = temp_time; + } + + /* If node is good. */ + if (!is_timeout(assoc->timestamp, BAD_NODE_TIMEOUT)) { + client_list[num_nodes] = client; + assoc_list[num_nodes] = assoc; + ++num_nodes; + } + } else { + ++sort; + + /* Timed out should be at beginning, if they are not, sort the list. */ + if (sort > 1 && sort < (((i + 1) * 2) - 1)) { + sort_ok = 1; + } + } + } + } + + if (sortable && sort_ok) { + sort_client_list(list, list_count, public_key); + } + + if ((num_nodes != 0) && (is_timeout(*lastgetnode, GET_NODE_INTERVAL) || *bootstrap_times < MAX_BOOTSTRAP_TIMES)) { + uint32_t rand_node = rand() % (num_nodes); + + if ((num_nodes - 1) != rand_node) { + rand_node += rand() % (num_nodes - (rand_node + 1)); + } + + getnodes(dht, assoc_list[rand_node]->ip_port, client_list[rand_node]->public_key, public_key, NULL); + + *lastgetnode = temp_time; + ++*bootstrap_times; + } + + return not_kill; +} + +/* Ping each client in the "friends" list every PING_INTERVAL seconds. Send a get nodes request + * every GET_NODE_INTERVAL seconds to a random good node for each "friend" in our "friends" list. + */ +static void do_DHT_friends(DHT *dht) +{ + for (size_t i = 0; i < dht->num_friends; ++i) { + DHT_Friend *dht_friend = &dht->friends_list[i]; + + for (size_t j = 0; j < dht_friend->num_to_bootstrap; ++j) { + getnodes(dht, dht_friend->to_bootstrap[j].ip_port, dht_friend->to_bootstrap[j].public_key, dht_friend->public_key, + NULL); + } + + dht_friend->num_to_bootstrap = 0; + + do_ping_and_sendnode_requests(dht, &dht_friend->lastgetnode, dht_friend->public_key, dht_friend->client_list, + MAX_FRIEND_CLIENTS, + &dht_friend->bootstrap_times, 1); + } +} + +/* Ping each client in the close nodes list every PING_INTERVAL seconds. + * Send a get nodes request every GET_NODE_INTERVAL seconds to a random good node in the list. + */ +static void do_Close(DHT *dht) +{ + for (size_t i = 0; i < dht->num_to_bootstrap; ++i) { + getnodes(dht, dht->to_bootstrap[i].ip_port, dht->to_bootstrap[i].public_key, dht->self_public_key, NULL); + } + + dht->num_to_bootstrap = 0; + + uint8_t not_killed = do_ping_and_sendnode_requests(dht, &dht->close_lastgetnodes, dht->self_public_key, + dht->close_clientlist, LCLIENT_LIST, &dht->close_bootstrap_times, 0); + + 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; + + for (size_t i = 0; i < LCLIENT_LIST; i++) { + Client_data *client = &dht->close_clientlist[i]; + + IPPTsPng *assocs[ASSOC_COUNT] = { &client->assoc6, &client->assoc4 }; + + for (size_t j = 0; j < ASSOC_COUNT; j++) { + IPPTsPng *assoc = assocs[j]; + + if (assoc->timestamp) { + assoc->timestamp = badonly; + } + } + } + } +} + +void DHT_getnodes(DHT *dht, const IP_Port *from_ipp, const uint8_t *from_id, const uint8_t *which_id) +{ + getnodes(dht, *from_ipp, from_id, which_id, NULL); +} + +void DHT_bootstrap(DHT *dht, IP_Port ip_port, const uint8_t *public_key) +{ + getnodes(dht, ip_port, public_key, dht->self_public_key, NULL); +} +int DHT_bootstrap_from_address(DHT *dht, const char *address, uint8_t ipv6enabled, + uint16_t port, const uint8_t *public_key) +{ + IP_Port ip_port_v64; + IP *ip_extra = NULL; + IP_Port ip_port_v4; + ip_init(&ip_port_v64.ip, ipv6enabled); + + if (ipv6enabled) { + /* setup for getting BOTH: an IPv6 AND an IPv4 address */ + ip_port_v64.ip.family = TOX_AF_UNSPEC; + ip_reset(&ip_port_v4.ip); + ip_extra = &ip_port_v4.ip; + } + + if (addr_resolve_or_parse_ip(address, &ip_port_v64.ip, ip_extra)) { + ip_port_v64.port = port; + DHT_bootstrap(dht, ip_port_v64, public_key); + + if ((ip_extra != NULL) && ip_isset(ip_extra)) { + ip_port_v4.port = port; + DHT_bootstrap(dht, ip_port_v4, public_key); + } + + return 1; + } + + return 0; +} + +/* Send the given packet to node with public_key + * + * return -1 if failure. + */ +int route_packet(const DHT *dht, const uint8_t *public_key, const uint8_t *packet, uint16_t length) +{ + for (uint32_t i = 0; i < LCLIENT_LIST; ++i) { + if (id_equal(public_key, dht->close_clientlist[i].public_key)) { + const Client_data *client = &dht->close_clientlist[i]; + const IPPTsPng *assocs[ASSOC_COUNT] = { &client->assoc6, &client->assoc4 }; + + for (size_t j = 0; j < ASSOC_COUNT; j++) { + const IPPTsPng *assoc = assocs[j]; + + if (ip_isset(&assoc->ip_port.ip)) { + return sendpacket(dht->net, assoc->ip_port, packet, length); + } + } + + break; + } + } + + return -1; +} + +/* Puts all the different ips returned by the nodes for a friend_num into array ip_portlist. + * ip_portlist must be at least MAX_FRIEND_CLIENTS big. + * + * return the number of ips returned. + * return 0 if we are connected to friend or if no ips were found. + * return -1 if no such friend. + */ +static int friend_iplist(const DHT *dht, IP_Port *ip_portlist, uint16_t friend_num) +{ + if (friend_num >= dht->num_friends) { + return -1; + } + + DHT_Friend *dht_friend = &dht->friends_list[friend_num]; + Client_data *client; + IP_Port ipv4s[MAX_FRIEND_CLIENTS]; + int num_ipv4s = 0; + IP_Port ipv6s[MAX_FRIEND_CLIENTS]; + int num_ipv6s = 0; + + for (size_t i = 0; i < MAX_FRIEND_CLIENTS; ++i) { + client = &(dht_friend->client_list[i]); + + /* 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; + } + + 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; + } + + if (id_equal(client->public_key, dht_friend->public_key)) { + 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 + memcpy(ip_portlist, ipv6s, num_ipv6s * sizeof(IP_Port)); + + if (num_ipv6s == MAX_FRIEND_CLIENTS) { + return MAX_FRIEND_CLIENTS; + } + + int num_ipv4s_used = MAX_FRIEND_CLIENTS - num_ipv6s; + + if (num_ipv4s_used > num_ipv4s) { + num_ipv4s_used = num_ipv4s; + } + + memcpy(&ip_portlist[num_ipv6s], ipv4s, num_ipv4s_used * sizeof(IP_Port)); + return num_ipv6s + num_ipv4s_used; + +#else /* !FRIEND_IPLIST_PAD */ + + /* there must be some secret reason why we can't pad the longer list + * with the shorter one... + */ + if (num_ipv6s >= num_ipv4s) { + memcpy(ip_portlist, ipv6s, num_ipv6s * sizeof(IP_Port)); + return num_ipv6s; + } + + memcpy(ip_portlist, ipv4s, num_ipv4s * sizeof(IP_Port)); + return num_ipv4s; + +#endif /* !FRIEND_IPLIST_PAD */ +} + + +/* Send the following packet to everyone who tells us they are connected to friend_id. + * + * return ip for friend. + * return number of nodes the packet was sent to. (Only works if more than (MAX_FRIEND_CLIENTS / 4). + */ +int route_tofriend(const DHT *dht, const uint8_t *friend_id, const uint8_t *packet, uint16_t length) +{ + uint32_t num = index_of_friend_pk(dht->friends_list, dht->num_friends, friend_id); + + if (num == UINT32_MAX) { + return 0; + } + + uint32_t sent = 0; + uint8_t friend_sent[MAX_FRIEND_CLIENTS] = {0}; + + IP_Port ip_list[MAX_FRIEND_CLIENTS]; + int ip_num = friend_iplist(dht, ip_list, num); + + if (ip_num < (MAX_FRIEND_CLIENTS / 4)) { + return 0; /* Reason for that? */ + } + + DHT_Friend *dht_friend = &dht->friends_list[num]; + Client_data *client; + + /* extra legwork, because having the outside allocating the space for us + * is *usually* good(tm) (bites us in the behind in this case though) */ + + for (uint32_t i = 0; i < MAX_FRIEND_CLIENTS; ++i) { + if (friend_sent[i]) {/* Send one packet per client.*/ + continue; + } + + client = &dht_friend->client_list[i]; + + const IPPTsPng *assocs[ASSOC_COUNT] = { &client->assoc4, &client->assoc6 }; + + for (size_t j = 0; j < ASSOC_COUNT; j++) { + const IPPTsPng *assoc = assocs[j]; + + /* If ip is not zero and node is good. */ + if (ip_isset(&assoc->ret_ip_port.ip) && !is_timeout(assoc->ret_timestamp, BAD_NODE_TIMEOUT)) { + int retval = sendpacket(dht->net, assoc->ip_port, packet, length); + + if ((unsigned int)retval == length) { + ++sent; + friend_sent[i] = 1; + } + } + } + } + + return sent; +} + +/* Send the following packet to one random person who tells us they are connected to friend_id. + * + * return number of nodes the packet was sent to. + */ +static int routeone_tofriend(DHT *dht, const uint8_t *friend_id, const uint8_t *packet, uint16_t length) +{ + uint32_t num = index_of_friend_pk(dht->friends_list, dht->num_friends, friend_id); + + if (num == UINT32_MAX) { + return 0; + } + + DHT_Friend *dht_friend = &dht->friends_list[num]; + Client_data *client; + + IP_Port ip_list[MAX_FRIEND_CLIENTS * 2]; + int n = 0; + + /* extra legwork, because having the outside allocating the space for us + * is *usually* good(tm) (bites us in the behind in this case though) */ + + for (uint32_t i = 0; i < MAX_FRIEND_CLIENTS; ++i) { + client = &dht_friend->client_list[i]; + + const IPPTsPng *assocs[ASSOC_COUNT] = { &client->assoc4, &client->assoc6 }; + + for (size_t j = 0; j < ASSOC_COUNT; j++) { + const IPPTsPng *assoc = assocs[j]; + + /* If ip is not zero and node is good. */ + if (ip_isset(&assoc->ret_ip_port.ip) && !is_timeout(assoc->ret_timestamp, BAD_NODE_TIMEOUT)) { + ip_list[n] = assoc->ip_port; + ++n; + } + } + } + + if (n < 1) { + return 0; + } + + int retval = sendpacket(dht->net, ip_list[rand() % n], packet, length); + + if ((unsigned int)retval == length) { + return 1; + } + + return 0; +} + +/*----------------------------------------------------------------------------------*/ +/*---------------------BEGINNING OF NAT PUNCHING FUNCTIONS--------------------------*/ + +static int send_NATping(DHT *dht, const uint8_t *public_key, uint64_t ping_id, uint8_t type) +{ + uint8_t data[sizeof(uint64_t) + 1]; + uint8_t packet[MAX_CRYPTO_REQUEST_SIZE]; + + int num = 0; + + data[0] = type; + memcpy(data + 1, &ping_id, sizeof(uint64_t)); + /* 254 is NAT ping request packet id */ + int len = create_request(dht->self_public_key, dht->self_secret_key, packet, public_key, data, + sizeof(uint64_t) + 1, CRYPTO_PACKET_NAT_PING); + + if (len == -1) { + return -1; + } + + if (type == 0) { /* If packet is request use many people to route it. */ + num = route_tofriend(dht, public_key, packet, len); + } else if (type == 1) { /* If packet is response use only one person to route it */ + num = routeone_tofriend(dht, public_key, packet, len); + } + + if (num == 0) { + return -1; + } + + return num; +} + +/* Handle a received ping request for. */ +static int handle_NATping(void *object, IP_Port source, const uint8_t *source_pubkey, const uint8_t *packet, + uint16_t length, void *userdata) +{ + if (length != sizeof(uint64_t) + 1) { + return 1; + } + + DHT *dht = (DHT *)object; + uint64_t ping_id; + memcpy(&ping_id, packet + 1, sizeof(uint64_t)); + + uint32_t friendnumber = index_of_friend_pk(dht->friends_list, dht->num_friends, source_pubkey); + + if (friendnumber == UINT32_MAX) { + return 1; + } + + DHT_Friend *dht_friend = &dht->friends_list[friendnumber]; + + if (packet[0] == NAT_PING_REQUEST) { + /* 1 is reply */ + send_NATping(dht, source_pubkey, ping_id, NAT_PING_RESPONSE); + dht_friend->nat.recvNATping_timestamp = unix_time(); + return 0; + } + + if (packet[0] == NAT_PING_RESPONSE) { + if (dht_friend->nat.NATping_id == ping_id) { + dht_friend->nat.NATping_id = random_64b(); + dht_friend->nat.hole_punching = 1; + return 0; + } + } + + return 1; +} + +/* Get the most common ip in the ip_portlist. + * Only return ip if it appears in list min_num or more. + * len must not be bigger than MAX_FRIEND_CLIENTS. + * + * return ip of 0 if failure. + */ +static IP NAT_commonip(IP_Port *ip_portlist, uint16_t len, uint16_t min_num) +{ + IP zero; + ip_reset(&zero); + + if (len > MAX_FRIEND_CLIENTS) { + return zero; + } + + uint16_t numbers[MAX_FRIEND_CLIENTS] = {0}; + + for (uint32_t i = 0; i < len; ++i) { + for (uint32_t j = 0; j < len; ++j) { + if (ip_equal(&ip_portlist[i].ip, &ip_portlist[j].ip)) { + ++numbers[i]; + } + } + + if (numbers[i] >= min_num) { + return ip_portlist[i].ip; + } + } + + return zero; +} + +/* Return all the ports for one ip in a list. + * portlist must be at least len long, + * where len is the length of ip_portlist. + * + * return number of ports and puts the list of ports in portlist. + */ +static uint16_t NAT_getports(uint16_t *portlist, IP_Port *ip_portlist, uint16_t len, IP ip) +{ + uint16_t num = 0; + + for (uint32_t i = 0; i < len; ++i) { + if (ip_equal(&ip_portlist[i].ip, &ip)) { + portlist[num] = net_ntohs(ip_portlist[i].port); + ++num; + } + } + + return num; +} + +static void punch_holes(DHT *dht, IP ip, uint16_t *port_list, uint16_t numports, uint16_t friend_num) +{ + if (!dht->hole_punching_enabled) { + return; + } + + if (numports > MAX_FRIEND_CLIENTS || numports == 0) { + return; + } + + uint16_t first_port = port_list[0]; + uint32_t i; + + for (i = 0; i < numports; ++i) { + if (first_port != port_list[i]) { + break; + } + } + + if (i == numports) { /* If all ports are the same, only try that one port. */ + IP_Port pinging; + ip_copy(&pinging.ip, &ip); + pinging.port = net_htons(first_port); + send_ping_request(dht->ping, pinging, dht->friends_list[friend_num].public_key); + } else { + for (i = 0; i < MAX_PUNCHING_PORTS; ++i) { + /* TODO(irungentoo): Improve port guessing algorithm. */ + uint32_t it = i + dht->friends_list[friend_num].nat.punching_index; + int8_t sign = (it % 2) ? -1 : 1; + uint32_t delta = sign * (it / (2 * numports)); + uint32_t index = (it / 2) % numports; + uint16_t port = port_list[index] + delta; + IP_Port pinging; + ip_copy(&pinging.ip, &ip); + pinging.port = net_htons(port); + send_ping_request(dht->ping, pinging, dht->friends_list[friend_num].public_key); + } + + dht->friends_list[friend_num].nat.punching_index += i; + } + + if (dht->friends_list[friend_num].nat.tries > MAX_NORMAL_PUNCHING_TRIES) { + uint16_t port = 1024; + IP_Port pinging; + ip_copy(&pinging.ip, &ip); + + for (i = 0; i < MAX_PUNCHING_PORTS; ++i) { + uint32_t it = i + dht->friends_list[friend_num].nat.punching_index2; + pinging.port = net_htons(port + it); + send_ping_request(dht->ping, pinging, dht->friends_list[friend_num].public_key); + } + + dht->friends_list[friend_num].nat.punching_index2 += i - (MAX_PUNCHING_PORTS / 2); + } + + ++dht->friends_list[friend_num].nat.tries; +} + +static void do_NAT(DHT *dht) +{ + uint64_t temp_time = unix_time(); + + for (uint32_t i = 0; i < dht->num_friends; ++i) { + IP_Port ip_list[MAX_FRIEND_CLIENTS]; + int num = friend_iplist(dht, ip_list, i); + + /* If already connected or friend is not online don't try to hole punch. */ + if (num < MAX_FRIEND_CLIENTS / 2) { + continue; + } + + if (dht->friends_list[i].nat.NATping_timestamp + PUNCH_INTERVAL < temp_time) { + send_NATping(dht, dht->friends_list[i].public_key, dht->friends_list[i].nat.NATping_id, NAT_PING_REQUEST); + dht->friends_list[i].nat.NATping_timestamp = temp_time; + } + + if (dht->friends_list[i].nat.hole_punching == 1 && + dht->friends_list[i].nat.punching_timestamp + PUNCH_INTERVAL < temp_time && + dht->friends_list[i].nat.recvNATping_timestamp + PUNCH_INTERVAL * 2 >= temp_time) { + + IP ip = NAT_commonip(ip_list, num, MAX_FRIEND_CLIENTS / 2); + + if (!ip_isset(&ip)) { + continue; + } + + if (dht->friends_list[i].nat.punching_timestamp + PUNCH_RESET_TIME < temp_time) { + dht->friends_list[i].nat.tries = 0; + dht->friends_list[i].nat.punching_index = 0; + dht->friends_list[i].nat.punching_index2 = 0; + } + + uint16_t port_list[MAX_FRIEND_CLIENTS]; + uint16_t numports = NAT_getports(port_list, ip_list, num, ip); + punch_holes(dht, ip, port_list, numports, i); + + dht->friends_list[i].nat.punching_timestamp = temp_time; + dht->friends_list[i].nat.hole_punching = 0; + } + } +} + +/*----------------------------------------------------------------------------------*/ +/*-----------------------END OF NAT PUNCHING FUNCTIONS------------------------------*/ + +#define HARDREQ_DATA_SIZE 384 /* Attempt to prevent amplification/other attacks*/ + +#define CHECK_TYPE_ROUTE_REQ 0 +#define CHECK_TYPE_ROUTE_RES 1 +#define CHECK_TYPE_GETNODE_REQ 2 +#define CHECK_TYPE_GETNODE_RES 3 +#define CHECK_TYPE_TEST_REQ 4 +#define CHECK_TYPE_TEST_RES 5 + +#if DHT_HARDENING +static int send_hardening_req(DHT *dht, Node_format *sendto, uint8_t type, uint8_t *contents, uint16_t length) +{ + if (length > HARDREQ_DATA_SIZE - 1) { + return -1; + } + + uint8_t packet[MAX_CRYPTO_REQUEST_SIZE]; + uint8_t data[HARDREQ_DATA_SIZE] = {0}; + data[0] = type; + memcpy(data + 1, contents, length); + int len = create_request(dht->self_public_key, dht->self_secret_key, packet, sendto->public_key, data, + sizeof(data), CRYPTO_PACKET_HARDENING); + + if (len == -1) { + return -1; + } + + return sendpacket(dht->net, sendto->ip_port, packet, len); +} + +/* Send a get node hardening request */ +static int send_hardening_getnode_req(DHT *dht, Node_format *dest, Node_format *node_totest, uint8_t *search_id) +{ + uint8_t data[sizeof(Node_format) + CRYPTO_PUBLIC_KEY_SIZE]; + memcpy(data, node_totest, sizeof(Node_format)); + memcpy(data + sizeof(Node_format), search_id, CRYPTO_PUBLIC_KEY_SIZE); + return send_hardening_req(dht, dest, CHECK_TYPE_GETNODE_REQ, data, sizeof(Node_format) + CRYPTO_PUBLIC_KEY_SIZE); +} +#endif + +/* Send a get node hardening response */ +static int send_hardening_getnode_res(const DHT *dht, const Node_format *sendto, const uint8_t *queried_client_id, + const uint8_t *nodes_data, uint16_t nodes_data_length) +{ + if (!ip_isset(&sendto->ip_port.ip)) { + return -1; + } + + uint8_t packet[MAX_CRYPTO_REQUEST_SIZE]; + VLA(uint8_t, data, 1 + CRYPTO_PUBLIC_KEY_SIZE + nodes_data_length); + data[0] = CHECK_TYPE_GETNODE_RES; + memcpy(data + 1, queried_client_id, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(data + 1 + CRYPTO_PUBLIC_KEY_SIZE, nodes_data, nodes_data_length); + int len = create_request(dht->self_public_key, dht->self_secret_key, packet, sendto->public_key, data, + SIZEOF_VLA(data), CRYPTO_PACKET_HARDENING); + + if (len == -1) { + return -1; + } + + return sendpacket(dht->net, sendto->ip_port, packet, len); +} + +/* TODO(irungentoo): improve */ +static IPPTsPng *get_closelist_IPPTsPng(DHT *dht, const uint8_t *public_key, Family sa_family) +{ + for (uint32_t i = 0; i < LCLIENT_LIST; ++i) { + if (!id_equal(dht->close_clientlist[i].public_key, public_key)) { + continue; + } + + if (sa_family == TOX_AF_INET) { + return &dht->close_clientlist[i].assoc4; + } + + if (sa_family == TOX_AF_INET6) { + return &dht->close_clientlist[i].assoc6; + } + } + + return NULL; +} + +/* + * check how many nodes in nodes are also present in the closelist. + * TODO(irungentoo): make this function better. + */ +static uint32_t have_nodes_closelist(DHT *dht, Node_format *nodes, uint16_t num) +{ + uint32_t counter = 0; + + for (uint32_t i = 0; i < num; ++i) { + if (id_equal(nodes[i].public_key, dht->self_public_key)) { + ++counter; + continue; + } + + IPPTsPng *temp = get_closelist_IPPTsPng(dht, nodes[i].public_key, nodes[i].ip_port.ip.family); + + if (temp) { + if (!is_timeout(temp->timestamp, BAD_NODE_TIMEOUT)) { + ++counter; + } + } + } + + return counter; +} + +/* Interval in seconds between hardening checks */ +#define HARDENING_INTERVAL 120 +#define HARDEN_TIMEOUT 1200 + +/* Handle a received hardening packet */ +static int handle_hardening(void *object, IP_Port source, const uint8_t *source_pubkey, const uint8_t *packet, + uint16_t length, void *userdata) +{ + DHT *dht = (DHT *)object; + + if (length < 2) { + return 1; + } + + switch (packet[0]) { + case CHECK_TYPE_GETNODE_REQ: { + if (length != HARDREQ_DATA_SIZE) { + return 1; + } + + Node_format node, tocheck_node; + node.ip_port = source; + memcpy(node.public_key, source_pubkey, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(&tocheck_node, packet + 1, sizeof(Node_format)); + + if (getnodes(dht, tocheck_node.ip_port, tocheck_node.public_key, packet + 1 + sizeof(Node_format), &node) == -1) { + return 1; + } + + return 0; + } + + case CHECK_TYPE_GETNODE_RES: { + if (length <= CRYPTO_PUBLIC_KEY_SIZE + 1) { + return 1; + } + + if (length > 1 + CRYPTO_PUBLIC_KEY_SIZE + sizeof(Node_format) * MAX_SENT_NODES) { + return 1; + } + + uint16_t length_nodes = length - 1 - CRYPTO_PUBLIC_KEY_SIZE; + Node_format nodes[MAX_SENT_NODES]; + int num_nodes = unpack_nodes(nodes, MAX_SENT_NODES, 0, packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, length_nodes, 0); + + /* TODO(irungentoo): MAX_SENT_NODES nodes should be returned at all times + (right now we have a small network size so it could cause problems for testing and etc..) */ + if (num_nodes <= 0) { + return 1; + } + + /* NOTE: This should work for now but should be changed to something better. */ + if (have_nodes_closelist(dht, nodes, num_nodes) < (uint32_t)((num_nodes + 2) / 2)) { + return 1; + } + + IPPTsPng *temp = get_closelist_IPPTsPng(dht, packet + 1, nodes[0].ip_port.ip.family); + + if (temp == NULL) { + return 1; + } + + if (is_timeout(temp->hardening.send_nodes_timestamp, HARDENING_INTERVAL)) { + return 1; + } + + if (!id_equal(temp->hardening.send_nodes_pingedid, source_pubkey)) { + return 1; + } + + /* If Nodes look good and the request checks out */ + temp->hardening.send_nodes_ok = 1; + return 0;/* success*/ + } + } + + return 1; +} + +#if DHT_HARDENING +/* Return a random node from all the nodes we are connected to. + * TODO(irungentoo): improve this function. + */ +static Node_format random_node(DHT *dht, Family sa_family) +{ + uint8_t id[CRYPTO_PUBLIC_KEY_SIZE]; + + for (uint32_t i = 0; i < CRYPTO_PUBLIC_KEY_SIZE / 4; ++i) { /* populate the id with pseudorandom bytes.*/ + uint32_t t = rand(); + memcpy(id + i * sizeof(t), &t, sizeof(t)); + } + + Node_format nodes_list[MAX_SENT_NODES]; + memset(nodes_list, 0, sizeof(nodes_list)); + uint32_t num_nodes = get_close_nodes(dht, id, nodes_list, sa_family, 1, 0); + + if (num_nodes == 0) { + return nodes_list[0]; + } + + return nodes_list[rand() % num_nodes]; +} +#endif + +/* Put up to max_num nodes in nodes from the closelist. + * + * return the number of nodes. + */ +static uint16_t list_nodes(Client_data *list, size_t length, Node_format *nodes, uint16_t max_num) +{ + if (max_num == 0) { + return 0; + } + + uint16_t count = 0; + + for (size_t i = length; i != 0; --i) { + IPPTsPng *assoc = NULL; + + if (!is_timeout(list[i - 1].assoc4.timestamp, BAD_NODE_TIMEOUT)) { + assoc = &list[i - 1].assoc4; + } + + if (!is_timeout(list[i - 1].assoc6.timestamp, BAD_NODE_TIMEOUT)) { + if (assoc == NULL) { + assoc = &list[i - 1].assoc6; + } else if (rand() % 2) { + assoc = &list[i - 1].assoc6; + } + } + + if (assoc != NULL) { + memcpy(nodes[count].public_key, list[i - 1].public_key, CRYPTO_PUBLIC_KEY_SIZE); + nodes[count].ip_port = assoc->ip_port; + ++count; + + if (count >= max_num) { + return count; + } + } + } + + return count; +} + +/* Put up to max_num nodes in nodes from the random friends. + * + * return the number of nodes. + */ +uint16_t randfriends_nodes(DHT *dht, Node_format *nodes, uint16_t max_num) +{ + if (max_num == 0) { + return 0; + } + + uint16_t count = 0; + unsigned int r = rand(); + + for (size_t i = 0; i < DHT_FAKE_FRIEND_NUMBER; ++i) { + count += list_nodes(dht->friends_list[(i + r) % DHT_FAKE_FRIEND_NUMBER].client_list, MAX_FRIEND_CLIENTS, nodes + count, + max_num - count); + + if (count >= max_num) { + break; + } + } + + return count; +} + +/* Put up to max_num nodes in nodes from the closelist. + * + * return the number of nodes. + */ +uint16_t closelist_nodes(DHT *dht, Node_format *nodes, uint16_t max_num) +{ + return list_nodes(dht->close_clientlist, LCLIENT_LIST, nodes, max_num); +} + +#if DHT_HARDENING +static void do_hardening(DHT *dht) +{ + for (uint32_t i = 0; i < LCLIENT_LIST * 2; ++i) { + IPPTsPng *cur_iptspng; + Family sa_family; + uint8_t *public_key = dht->close_clientlist[i / 2].public_key; + + if (i % 2 == 0) { + cur_iptspng = &dht->close_clientlist[i / 2].assoc4; + sa_family = TOX_AF_INET; + } else { + cur_iptspng = &dht->close_clientlist[i / 2].assoc6; + sa_family = TOX_AF_INET6; + } + + if (is_timeout(cur_iptspng->timestamp, BAD_NODE_TIMEOUT)) { + continue; + } + + if (cur_iptspng->hardening.send_nodes_ok == 0) { + if (is_timeout(cur_iptspng->hardening.send_nodes_timestamp, HARDENING_INTERVAL)) { + Node_format rand_node = random_node(dht, sa_family); + + if (!ipport_isset(&rand_node.ip_port)) { + continue; + } + + if (id_equal(public_key, rand_node.public_key)) { + continue; + } + + Node_format to_test; + to_test.ip_port = cur_iptspng->ip_port; + memcpy(to_test.public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); + + // TODO(irungentoo): The search id should maybe not be ours? + if (send_hardening_getnode_req(dht, &rand_node, &to_test, dht->self_public_key) > 0) { + memcpy(cur_iptspng->hardening.send_nodes_pingedid, rand_node.public_key, CRYPTO_PUBLIC_KEY_SIZE); + cur_iptspng->hardening.send_nodes_timestamp = unix_time(); + } + } + } else { + if (is_timeout(cur_iptspng->hardening.send_nodes_timestamp, HARDEN_TIMEOUT)) { + cur_iptspng->hardening.send_nodes_ok = 0; + } + } + + // TODO(irungentoo): add the 2 other testers. + } +} +#endif + +/*----------------------------------------------------------------------------------*/ + +void cryptopacket_registerhandler(DHT *dht, uint8_t byte, cryptopacket_handler_callback cb, void *object) +{ + dht->cryptopackethandlers[byte].function = cb; + dht->cryptopackethandlers[byte].object = object; +} + +static int cryptopacket_handle(void *object, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) +{ + DHT *dht = (DHT *)object; + + assert(packet[0] == NET_PACKET_CRYPTO); + + if (length <= CRYPTO_PUBLIC_KEY_SIZE * 2 + CRYPTO_NONCE_SIZE + 1 + CRYPTO_MAC_SIZE || + length > MAX_CRYPTO_REQUEST_SIZE + CRYPTO_MAC_SIZE) { + return 1; + } + + // Check if request is for us. + if (id_equal(packet + 1, dht->self_public_key)) { + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t data[MAX_CRYPTO_REQUEST_SIZE]; + uint8_t number; + int len = handle_request(dht->self_public_key, dht->self_secret_key, public_key, data, &number, packet, length); + + if (len == -1 || len == 0) { + return 1; + } + + if (!dht->cryptopackethandlers[number].function) { + return 1; + } + + return dht->cryptopackethandlers[number].function(dht->cryptopackethandlers[number].object, source, public_key, + data, len, userdata); + } + + /* If request is not for us, try routing it. */ + int retval = route_packet(dht, packet + 1, packet, length); + + if ((unsigned int)retval == length) { + return 0; + } + + return 1; +} + +/*----------------------------------------------------------------------------------*/ + +DHT *new_DHT(Logger *log, Networking_Core *net, bool holepunching_enabled) +{ + /* init time */ + unix_time_update(); + + if (net == NULL) { + return NULL; + } + + DHT *dht = (DHT *)calloc(1, sizeof(DHT)); + + if (dht == NULL) { + return NULL; + } + + dht->log = log; + dht->net = net; + + dht->hole_punching_enabled = holepunching_enabled; + + dht->ping = new_ping(dht); + + if (dht->ping == NULL) { + kill_DHT(dht); + return NULL; + } + + networking_registerhandler(dht->net, NET_PACKET_GET_NODES, &handle_getnodes, dht); + networking_registerhandler(dht->net, NET_PACKET_SEND_NODES_IPV6, &handle_sendnodes_ipv6, dht); + networking_registerhandler(dht->net, NET_PACKET_CRYPTO, &cryptopacket_handle, dht); + cryptopacket_registerhandler(dht, CRYPTO_PACKET_NAT_PING, &handle_NATping, dht); + cryptopacket_registerhandler(dht, CRYPTO_PACKET_HARDENING, &handle_hardening, dht); + + new_symmetric_key(dht->secret_symmetric_key); + crypto_new_keypair(dht->self_public_key, dht->self_secret_key); + + ping_array_init(&dht->dht_ping_array, DHT_PING_ARRAY_SIZE, PING_TIMEOUT); + ping_array_init(&dht->dht_harden_ping_array, DHT_PING_ARRAY_SIZE, PING_TIMEOUT); + + for (uint32_t i = 0; i < DHT_FAKE_FRIEND_NUMBER; ++i) { + uint8_t random_key_bytes[CRYPTO_PUBLIC_KEY_SIZE]; + random_bytes(random_key_bytes, sizeof(random_key_bytes)); + + if (DHT_addfriend(dht, random_key_bytes, 0, 0, 0, 0) != 0) { + kill_DHT(dht); + return NULL; + } + } + + return dht; +} + +void do_DHT(DHT *dht) +{ + unix_time_update(); + + if (dht->last_run == unix_time()) { + return; + } + + // Load friends/clients if first call to do_DHT + if (dht->loaded_num_nodes) { + DHT_connect_after_load(dht); + } + + do_Close(dht); + do_DHT_friends(dht); + do_NAT(dht); + do_to_ping(dht->ping); +#if DHT_HARDENING + do_hardening(dht); +#endif + dht->last_run = unix_time(); +} +void kill_DHT(DHT *dht) +{ + networking_registerhandler(dht->net, NET_PACKET_GET_NODES, NULL, NULL); + networking_registerhandler(dht->net, NET_PACKET_SEND_NODES_IPV6, NULL, NULL); + cryptopacket_registerhandler(dht, CRYPTO_PACKET_NAT_PING, NULL, NULL); + cryptopacket_registerhandler(dht, CRYPTO_PACKET_HARDENING, NULL, NULL); + ping_array_free_all(&dht->dht_ping_array); + ping_array_free_all(&dht->dht_harden_ping_array); + kill_ping(dht->ping); + free(dht->friends_list); + free(dht->loaded_nodes_list); + free(dht); +} + +/* new DHT format for load/save, more robust and forward compatible */ +// TODO(irungentoo): Move this closer to Messenger. +#define DHT_STATE_COOKIE_GLOBAL 0x159000d + +#define DHT_STATE_COOKIE_TYPE 0x11ce +#define DHT_STATE_TYPE_NODES 4 + +#define MAX_SAVED_DHT_NODES (((DHT_FAKE_FRIEND_NUMBER * MAX_FRIEND_CLIENTS) + LCLIENT_LIST) * 2) + +/* Get the size of the DHT (for saving). */ +uint32_t DHT_size(const DHT *dht) +{ + uint32_t numv4 = 0, numv6 = 0; + + for (uint32_t i = 0; i < LCLIENT_LIST; ++i) { + numv4 += (dht->close_clientlist[i].assoc4.timestamp != 0); + numv6 += (dht->close_clientlist[i].assoc6.timestamp != 0); + } + + for (uint32_t i = 0; i < DHT_FAKE_FRIEND_NUMBER && i < dht->num_friends; ++i) { + DHT_Friend *fr = &dht->friends_list[i]; + + for (uint32_t j = 0; j < MAX_FRIEND_CLIENTS; ++j) { + numv4 += (fr->client_list[j].assoc4.timestamp != 0); + numv6 += (fr->client_list[j].assoc6.timestamp != 0); + } + } + + uint32_t size32 = sizeof(uint32_t), sizesubhead = size32 * 2; + + return size32 + sizesubhead + (packed_node_size(TOX_AF_INET) * numv4) + (packed_node_size(TOX_AF_INET6) * numv6); +} + +static uint8_t *DHT_save_subheader(uint8_t *data, uint32_t len, uint16_t type) +{ + host_to_lendian32(data, len); + data += sizeof(uint32_t); + host_to_lendian32(data, (host_tolendian16(DHT_STATE_COOKIE_TYPE) << 16) | host_tolendian16(type)); + data += sizeof(uint32_t); + return data; +} + + +/* Save the DHT in data where data is an array of size DHT_size(). */ +void DHT_save(DHT *dht, uint8_t *data) +{ + host_to_lendian32(data, DHT_STATE_COOKIE_GLOBAL); + data += sizeof(uint32_t); + + uint8_t *old_data = data; + + /* get right offset. we write the actual header later. */ + data = DHT_save_subheader(data, 0, 0); + + Node_format clients[MAX_SAVED_DHT_NODES]; + + uint32_t num = 0; + + for (uint32_t i = 0; i < LCLIENT_LIST; ++i) { + if (dht->close_clientlist[i].assoc4.timestamp != 0) { + memcpy(clients[num].public_key, dht->close_clientlist[i].public_key, CRYPTO_PUBLIC_KEY_SIZE); + clients[num].ip_port = dht->close_clientlist[i].assoc4.ip_port; + ++num; + } + + if (dht->close_clientlist[i].assoc6.timestamp != 0) { + memcpy(clients[num].public_key, dht->close_clientlist[i].public_key, CRYPTO_PUBLIC_KEY_SIZE); + clients[num].ip_port = dht->close_clientlist[i].assoc6.ip_port; + ++num; + } + } + + for (uint32_t i = 0; i < DHT_FAKE_FRIEND_NUMBER && i < dht->num_friends; ++i) { + DHT_Friend *fr = &dht->friends_list[i]; + + for (uint32_t j = 0; j < MAX_FRIEND_CLIENTS; ++j) { + if (fr->client_list[j].assoc4.timestamp != 0) { + memcpy(clients[num].public_key, fr->client_list[j].public_key, CRYPTO_PUBLIC_KEY_SIZE); + clients[num].ip_port = fr->client_list[j].assoc4.ip_port; + ++num; + } + + if (fr->client_list[j].assoc6.timestamp != 0) { + memcpy(clients[num].public_key, fr->client_list[j].public_key, CRYPTO_PUBLIC_KEY_SIZE); + clients[num].ip_port = fr->client_list[j].assoc6.ip_port; + ++num; + } + } + } + + DHT_save_subheader(old_data, pack_nodes(data, sizeof(Node_format) * num, clients, num), DHT_STATE_TYPE_NODES); +} + +/* Bootstrap from this number of nodes every time DHT_connect_after_load() is called */ +#define SAVE_BOOTSTAP_FREQUENCY 8 + +/* Start sending packets after DHT loaded_friends_list and loaded_clients_list are set */ +int DHT_connect_after_load(DHT *dht) +{ + if (dht == NULL) { + return -1; + } + + if (!dht->loaded_nodes_list) { + return -1; + } + + /* DHT is connected, stop. */ + if (DHT_non_lan_connected(dht)) { + free(dht->loaded_nodes_list); + dht->loaded_nodes_list = NULL; + dht->loaded_num_nodes = 0; + return 0; + } + + for (uint32_t i = 0; i < dht->loaded_num_nodes && i < SAVE_BOOTSTAP_FREQUENCY; ++i) { + unsigned int index = dht->loaded_nodes_index % dht->loaded_num_nodes; + DHT_bootstrap(dht, dht->loaded_nodes_list[index].ip_port, dht->loaded_nodes_list[index].public_key); + ++dht->loaded_nodes_index; + } + + return 0; +} + +static int dht_load_state_callback(void *outer, const uint8_t *data, uint32_t length, uint16_t type) +{ + DHT *dht = (DHT *)outer; + + switch (type) { + case DHT_STATE_TYPE_NODES: + if (length == 0) { + break; + } + + { + free(dht->loaded_nodes_list); + // Copy to loaded_clients_list + dht->loaded_nodes_list = (Node_format *)calloc(MAX_SAVED_DHT_NODES, sizeof(Node_format)); + + int num = unpack_nodes(dht->loaded_nodes_list, MAX_SAVED_DHT_NODES, NULL, data, length, 0); + + if (num > 0) { + dht->loaded_num_nodes = num; + } else { + dht->loaded_num_nodes = 0; + } + } /* localize declarations */ + + break; + + default: + LOGGER_ERROR(dht->log, "Load state (DHT): contains unrecognized part (len %u, type %u)\n", + length, type); + break; + } + + return 0; +} + +/* Load the DHT from data of size size. + * + * return -1 if failure. + * return 0 if success. + */ +int DHT_load(DHT *dht, const uint8_t *data, uint32_t length) +{ + uint32_t cookie_len = sizeof(uint32_t); + + if (length > cookie_len) { + uint32_t data32; + lendian_to_host32(&data32, data); + + if (data32 == DHT_STATE_COOKIE_GLOBAL) { + return load_state(dht_load_state_callback, dht->log, dht, data + cookie_len, + length - cookie_len, DHT_STATE_COOKIE_TYPE); + } + } + + return -1; +} + +/* return 0 if we are not connected to the DHT. + * return 1 if we are. + */ +int DHT_isconnected(const DHT *dht) +{ + unix_time_update(); + + for (uint32_t i = 0; i < LCLIENT_LIST; ++i) { + const Client_data *client = &dht->close_clientlist[i]; + + if (!is_timeout(client->assoc4.timestamp, BAD_NODE_TIMEOUT) || + !is_timeout(client->assoc6.timestamp, BAD_NODE_TIMEOUT)) { + return 1; + } + } + + return 0; +} + +/* return 0 if we are not connected or only connected to lan peers with the DHT. + * return 1 if we are. + */ +int DHT_non_lan_connected(const DHT *dht) +{ + unix_time_update(); + + for (uint32_t i = 0; i < LCLIENT_LIST; ++i) { + const Client_data *client = &dht->close_clientlist[i]; + + if (!is_timeout(client->assoc4.timestamp, BAD_NODE_TIMEOUT) && LAN_ip(client->assoc4.ip_port.ip) == -1) { + return 1; + } + + if (!is_timeout(client->assoc6.timestamp, BAD_NODE_TIMEOUT) && LAN_ip(client->assoc6.ip_port.ip) == -1) { + return 1; + } + } + + return 0; +} diff --git a/libs/libtox/src/toxcore/DHT.h b/libs/libtox/src/toxcore/DHT.h new file mode 100644 index 0000000000..510b3c5c92 --- /dev/null +++ b/libs/libtox/src/toxcore/DHT.h @@ -0,0 +1,448 @@ +/* + * An implementation of the DHT as seen in docs/updates/DHT.md + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2013 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifndef DHT_H +#define DHT_H + +#include "crypto_core.h" +#include "logger.h" +#include "network.h" +#include "ping_array.h" + +#include <stdbool.h> + +/* Maximum number of clients stored per friend. */ +#define MAX_FRIEND_CLIENTS 8 + +#define LCLIENT_NODES (MAX_FRIEND_CLIENTS) +#define LCLIENT_LENGTH 128 + +/* A list of the clients mathematically closest to ours. */ +#define LCLIENT_LIST (LCLIENT_LENGTH * LCLIENT_NODES) + +#define MAX_CLOSE_TO_BOOTSTRAP_NODES 8 + +/* The max number of nodes to send with send nodes. */ +#define MAX_SENT_NODES 4 + +/* Ping timeout in seconds */ +#define PING_TIMEOUT 5 + +/* size of DHT ping arrays. */ +#define DHT_PING_ARRAY_SIZE 512 + +/* 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 1 +#define PING_ROUNDTRIP 2 +#define BAD_NODE_TIMEOUT (PING_INTERVAL + PINGS_MISSED_NODE_GOES_BAD * (PING_INTERVAL + PING_ROUNDTRIP)) + +/* The number of "fake" friends to add (for optimization purposes and so our paths for the onion part are more random) */ +#define DHT_FAKE_FRIEND_NUMBER 2 + +#define MAX_CRYPTO_REQUEST_SIZE 1024 + +#define CRYPTO_PACKET_FRIEND_REQ 32 /* Friend request crypto packet ID. */ +#define CRYPTO_PACKET_HARDENING 48 /* Hardening crypto packet ID. */ +#define CRYPTO_PACKET_DHTPK 156 +#define CRYPTO_PACKET_NAT_PING 254 /* NAT ping crypto packet ID. */ + +/* Create a request to peer. + * send_public_key and send_secret_key are the pub/secret keys of the sender. + * recv_public_key is public key of receiver. + * packet must be an array of MAX_CRYPTO_REQUEST_SIZE big. + * Data represents the data we send with the request with length being the length of the data. + * request_id is the id of the request (32 = friend request, 254 = ping request). + * + * return -1 on failure. + * return the length of the created packet on success. + */ +int create_request(const uint8_t *send_public_key, const uint8_t *send_secret_key, uint8_t *packet, + const uint8_t *recv_public_key, const uint8_t *data, uint32_t length, uint8_t request_id); + +/* puts the senders public key in the request in public_key, the data from the request + in data if a friend or ping request was sent to us and returns the length of the data. + packet is the request packet and length is its length + return -1 if not valid request. */ +int handle_request(const uint8_t *self_public_key, const uint8_t *self_secret_key, uint8_t *public_key, uint8_t *data, + uint8_t *request_id, const uint8_t *packet, uint16_t length); + +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; + /* Time which we last checked this.*/ + uint64_t routes_requests_timestamp; + uint8_t routes_requests_pingedid[CRYPTO_PUBLIC_KEY_SIZE]; + /* Node sends correct send_node (true (1) or false/didn't check (0)) */ + uint8_t send_nodes_ok; + /* Time which we last checked this.*/ + uint64_t send_nodes_timestamp; + uint8_t send_nodes_pingedid[CRYPTO_PUBLIC_KEY_SIZE]; + /* Node can be used to test other nodes (true (1) or false/didn't check (0)) */ + uint8_t testing_requests; + /* Time which we last checked this.*/ + uint64_t testing_timestamp; + uint8_t testing_pingedid[CRYPTO_PUBLIC_KEY_SIZE]; +} Hardening; + +typedef struct { + IP_Port ip_port; + uint64_t timestamp; + uint64_t last_pinged; + + Hardening hardening; + /* Returned by this node. Either our friend or us. */ + IP_Port ret_ip_port; + uint64_t ret_timestamp; +} IPPTsPng; + +typedef struct { + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; + IPPTsPng assoc4; + IPPTsPng assoc6; +} Client_data; + +/*----------------------------------------------------------------------------------*/ + +typedef struct { + /* 1 if currently hole punching, otherwise 0 */ + uint8_t hole_punching; + uint32_t punching_index; + uint32_t tries; + uint32_t punching_index2; + + uint64_t punching_timestamp; + uint64_t recvNATping_timestamp; + uint64_t NATping_id; + uint64_t NATping_timestamp; +} NAT; + +#define DHT_FRIEND_MAX_LOCKS 32 + +typedef struct { + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; + IP_Port ip_port; +} +Node_format; + +typedef struct { + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; + Client_data client_list[MAX_FRIEND_CLIENTS]; + + /* Time at which the last get_nodes request was sent. */ + uint64_t lastgetnode; + /* number of times get_node packets were sent. */ + uint32_t bootstrap_times; + + /* Symetric NAT hole punching stuff. */ + NAT nat; + + uint16_t lock_count; + struct { + void (*ip_callback)(void *, int32_t, IP_Port); + void *data; + int32_t number; + } callbacks[DHT_FRIEND_MAX_LOCKS]; + + Node_format to_bootstrap[MAX_SENT_NODES]; + unsigned int num_to_bootstrap; +} DHT_Friend; + +/* Return packet size of packed node with ip_family on success. + * Return -1 on failure. + */ +int packed_node_size(uint8_t ip_family); + +/* Pack number of nodes into data of maxlength length. + * + * return length of packed nodes on success. + * return -1 on failure. + */ +int pack_nodes(uint8_t *data, uint16_t length, const Node_format *nodes, uint16_t number); + +/* Unpack data of length into nodes of size max_num_nodes. + * Put the length of the data processed in processed_data_len. + * tcp_enabled sets if TCP nodes are expected (true) or not (false). + * + * return number of unpacked nodes on success. + * return -1 on failure. + */ +int unpack_nodes(Node_format *nodes, uint16_t max_num_nodes, uint16_t *processed_data_len, const uint8_t *data, + uint16_t length, uint8_t tcp_enabled); + + +/*----------------------------------------------------------------------------------*/ +/* struct to store some shared keys so we don't have to regenerate them for each request. */ +#define MAX_KEYS_PER_SLOT 4 +#define KEYS_TIMEOUT 600 + +typedef struct { + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + uint32_t times_requested; + uint8_t stored; /* 0 if not, 1 if is */ + uint64_t time_last_requested; +} Shared_Key; + +typedef struct { + Shared_Key keys[256 * MAX_KEYS_PER_SLOT]; +} Shared_Keys; + +/*----------------------------------------------------------------------------------*/ + +typedef int (*cryptopacket_handler_callback)(void *object, IP_Port ip_port, const uint8_t *source_pubkey, + const uint8_t *data, uint16_t len, void *userdata); + +typedef struct { + cryptopacket_handler_callback function; + void *object; +} Cryptopacket_Handles; + +typedef struct { + Logger *log; + Networking_Core *net; + + bool hole_punching_enabled; + + Client_data close_clientlist[LCLIENT_LIST]; + uint64_t close_lastgetnodes; + uint32_t close_bootstrap_times; + + /* Note: this key should not be/is not used to transmit any sensitive materials */ + uint8_t secret_symmetric_key[CRYPTO_SYMMETRIC_KEY_SIZE]; + /* DHT keypair */ + uint8_t self_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t self_secret_key[CRYPTO_SECRET_KEY_SIZE]; + + DHT_Friend *friends_list; + uint16_t num_friends; + + Node_format *loaded_nodes_list; + uint32_t loaded_num_nodes; + unsigned int loaded_nodes_index; + + Shared_Keys shared_keys_recv; + Shared_Keys shared_keys_sent; + + struct PING *ping; + Ping_Array dht_ping_array; + Ping_Array dht_harden_ping_array; + uint64_t last_run; + + Cryptopacket_Handles cryptopackethandlers[256]; + + Node_format to_bootstrap[MAX_CLOSE_TO_BOOTSTRAP_NODES]; + unsigned int num_to_bootstrap; +} DHT; +/*----------------------------------------------------------------------------------*/ + +/* Shared key generations are costly, it is therefor smart to store commonly used + * ones so that they can re used later without being computed again. + * + * If shared key is already in shared_keys, copy it to shared_key. + * else generate it into shared_key and copy it to shared_keys + */ +void get_shared_key(Shared_Keys *shared_keys, uint8_t *shared_key, const uint8_t *secret_key, + const uint8_t *public_key); + +/* Copy shared_key to encrypt/decrypt DHT packet from public_key into shared_key + * for packets that we receive. + */ +void DHT_get_shared_key_recv(DHT *dht, uint8_t *shared_key, const uint8_t *public_key); + +/* Copy shared_key to encrypt/decrypt DHT packet from public_key into shared_key + * for packets that we send. + */ +void DHT_get_shared_key_sent(DHT *dht, uint8_t *shared_key, const uint8_t *public_key); + +void DHT_getnodes(DHT *dht, const IP_Port *from_ipp, const uint8_t *from_id, const uint8_t *which_id); + +/* Add a new friend to the friends list. + * public_key must be CRYPTO_PUBLIC_KEY_SIZE bytes long. + * + * ip_callback is the callback of a function that will be called when the ip address + * is found along with arguments data and number. + * + * lock_count will be set to a non zero number that must be passed to DHT_delfriend() + * to properly remove the callback. + * + * return 0 if success. + * return -1 if failure (friends list is full). + */ +int DHT_addfriend(DHT *dht, const uint8_t *public_key, void (*ip_callback)(void *data, int32_t number, IP_Port), + void *data, int32_t number, uint16_t *lock_count); + +/* Delete a friend from the friends list. + * public_key must be CRYPTO_PUBLIC_KEY_SIZE bytes long. + * + * return 0 if success. + * return -1 if failure (public_key not in friends list). + */ +int DHT_delfriend(DHT *dht, const uint8_t *public_key, uint16_t lock_count); + +/* Get ip of friend. + * public_key must be CRYPTO_PUBLIC_KEY_SIZE bytes long. + * ip must be 4 bytes long. + * port must be 2 bytes long. + * + * int DHT_getfriendip(DHT *dht, uint8_t *public_key, IP_Port *ip_port); + * + * return -1, -- if public_key does NOT refer to a friend + * return 0, -- if public_key refers to a friend and we failed to find the friend (yet) + * return 1, ip if public_key refers to a friend and we found him + */ +int DHT_getfriendip(const DHT *dht, const uint8_t *public_key, IP_Port *ip_port); + +/* Compares pk1 and pk2 with pk. + * + * return 0 if both are same distance. + * return 1 if pk1 is closer. + * return 2 if pk2 is closer. + */ +int id_closest(const uint8_t *pk, const uint8_t *pk1, const uint8_t *pk2); + +/* Add node to the node list making sure only the nodes closest to cmp_pk are in the list. + */ +bool add_to_list(Node_format *nodes_list, unsigned int length, const uint8_t *pk, IP_Port ip_port, + const uint8_t *cmp_pk); + +/* Return 1 if node can be added to close list, 0 if it can't. + */ +bool node_addable_to_close_list(DHT *dht, const uint8_t *public_key, IP_Port ip_port); + +/* Get the (maximum MAX_SENT_NODES) closest nodes to public_key we know + * and put them in nodes_list (must be MAX_SENT_NODES big). + * + * sa_family = family (IPv4 or IPv6) (0 if we don't care)? + * is_LAN = return some LAN ips (true or false) + * want_good = do we want tested nodes or not? (TODO(irungentoo)) + * + * return the number of nodes returned. + * + */ +int get_close_nodes(const DHT *dht, const uint8_t *public_key, Node_format *nodes_list, Family sa_family, + uint8_t is_LAN, uint8_t want_good); + + +/* Put up to max_num nodes in nodes from the random friends. + * + * return the number of nodes. + */ +uint16_t randfriends_nodes(DHT *dht, Node_format *nodes, uint16_t max_num); + +/* Put up to max_num nodes in nodes from the closelist. + * + * return the number of nodes. + */ +uint16_t closelist_nodes(DHT *dht, Node_format *nodes, uint16_t max_num); + +/* Run this function at least a couple times per second (It's the main loop). */ +void do_DHT(DHT *dht); + +/* + * Use these two functions to bootstrap the client. + */ +/* Sends a "get nodes" request to the given node with ip, port and public_key + * to setup connections + */ +void DHT_bootstrap(DHT *dht, IP_Port ip_port, const uint8_t *public_key); +/* Resolves address into an IP address. If successful, sends a "get nodes" + * request to the given node with ip, port and public_key to setup connections + * + * address can be a hostname or an IP address (IPv4 or IPv6). + * if ipv6enabled is 0 (zero), the resolving sticks STRICTLY to IPv4 addresses + * if ipv6enabled is not 0 (zero), the resolving looks for IPv6 addresses first, + * then IPv4 addresses. + * + * returns 1 if the address could be converted into an IP address + * returns 0 otherwise + */ +int DHT_bootstrap_from_address(DHT *dht, const char *address, uint8_t ipv6enabled, + uint16_t port, const uint8_t *public_key); + +/* Start sending packets after DHT loaded_friends_list and loaded_clients_list are set. + * + * returns 0 if successful + * returns -1 otherwise + */ +int DHT_connect_after_load(DHT *dht); + +/* ROUTING FUNCTIONS */ + +/* Send the given packet to node with public_key. + * + * return -1 if failure. + */ +int route_packet(const DHT *dht, const uint8_t *public_key, const uint8_t *packet, uint16_t length); + +/* Send the following packet to everyone who tells us they are connected to friend_id. + * + * return number of nodes it sent the packet to. + */ +int route_tofriend(const DHT *dht, const uint8_t *friend_id, const uint8_t *packet, uint16_t length); + +/* Function to handle crypto packets. + */ +void cryptopacket_registerhandler(DHT *dht, uint8_t byte, cryptopacket_handler_callback cb, void *object); + +/* SAVE/LOAD functions */ + +/* Get the size of the DHT (for saving). */ +uint32_t DHT_size(const DHT *dht); + +/* Save the DHT in data where data is an array of size DHT_size(). */ +void DHT_save(DHT *dht, uint8_t *data); + +/* Load the DHT from data of size size. + * + * return -1 if failure. + * return 0 if success. + */ +int DHT_load(DHT *dht, const uint8_t *data, uint32_t length); + +/* Initialize DHT. */ +DHT *new_DHT(Logger *log, Networking_Core *net, bool holepunching_enabled); + +void kill_DHT(DHT *dht); + +/* return 0 if we are not connected to the DHT. + * return 1 if we are. + */ +int DHT_isconnected(const DHT *dht); + +/* return 0 if we are not connected or only connected to lan peers with the DHT. + * return 1 if we are. + */ +int DHT_non_lan_connected(const DHT *dht); + + +uint32_t addto_lists(DHT *dht, IP_Port ip_port, const uint8_t *public_key); + +#endif diff --git a/libs/libtox/src/toxcore/LAN_discovery.c b/libs/libtox/src/toxcore/LAN_discovery.c new file mode 100644 index 0000000000..b95e401d05 --- /dev/null +++ b/libs/libtox/src/toxcore/LAN_discovery.c @@ -0,0 +1,410 @@ +/* + * LAN discovery implementation. + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2013 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "LAN_discovery.h" + +#include "util.h" + +/* Used for get_broadcast(). */ +#ifdef __linux +#include <linux/netdevice.h> +#include <sys/ioctl.h> +#endif + +#define MAX_INTERFACES 16 + + +/* TODO: multiple threads might concurrently try to set these, and it isn't clear that this couldn't lead to undesirable + * behaviour. Consider storing the data in per-instance variables instead. */ +static int broadcast_count = -1; +static IP_Port broadcast_ip_ports[MAX_INTERFACES]; + +#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) + +#include <iphlpapi.h> + +static void fetch_broadcast_info(uint16_t port) +{ + IP_ADAPTER_INFO *pAdapterInfo = (IP_ADAPTER_INFO *)malloc(sizeof(IP_ADAPTER_INFO)); + unsigned long ulOutBufLen = sizeof(IP_ADAPTER_INFO); + + if (pAdapterInfo == NULL) { + return; + } + + if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) == ERROR_BUFFER_OVERFLOW) { + free(pAdapterInfo); + pAdapterInfo = (IP_ADAPTER_INFO *)malloc(ulOutBufLen); + + if (pAdapterInfo == NULL) { + return; + } + } + + /* We copy these to the static variables broadcast_* only at the end of fetch_broadcast_info(). + * The intention is to ensure that even if multiple threads enter fetch_broadcast_info() concurrently, only valid + * interfaces will be set to be broadcast to. + * */ + int count = 0; + IP_Port ip_ports[MAX_INTERFACES]; + + int ret; + + if ((ret = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen)) == NO_ERROR) { + IP_ADAPTER_INFO *pAdapter = pAdapterInfo; + + while (pAdapter) { + IP gateway = {0}, subnet_mask = {0}; + + if (addr_parse_ip(pAdapter->IpAddressList.IpMask.String, &subnet_mask) + && addr_parse_ip(pAdapter->GatewayList.IpAddress.String, &gateway)) { + if (gateway.family == TOX_AF_INET && subnet_mask.family == TOX_AF_INET) { + IP_Port *ip_port = &ip_ports[count]; + ip_port->ip.family = TOX_AF_INET; + uint32_t gateway_ip = net_ntohl(gateway.ip4.uint32), subnet_ip = net_ntohl(subnet_mask.ip4.uint32); + uint32_t broadcast_ip = gateway_ip + ~subnet_ip - 1; + ip_port->ip.ip4.uint32 = net_htonl(broadcast_ip); + ip_port->port = port; + count++; + + if (count >= MAX_INTERFACES) { + break; + } + } + } + + pAdapter = pAdapter->Next; + } + } + + if (pAdapterInfo) { + free(pAdapterInfo); + } + + broadcast_count = count; + + for (uint32_t i = 0; i < count; i++) { + broadcast_ip_ports[i] = ip_ports[i]; + } +} + +#elif defined(__linux__) + +static void fetch_broadcast_info(uint16_t port) +{ + /* Not sure how many platforms this will run on, + * so it's wrapped in __linux for now. + * Definitely won't work like this on Windows... + */ + broadcast_count = 0; + Socket sock = 0; + + if ((sock = net_socket(TOX_AF_INET, TOX_SOCK_STREAM, 0)) < 0) { + return; + } + + /* Configure ifconf for the ioctl call. */ + struct ifreq i_faces[MAX_INTERFACES]; + memset(i_faces, 0, sizeof(struct ifreq) * MAX_INTERFACES); + + struct ifconf ifconf; + ifconf.ifc_buf = (char *)i_faces; + ifconf.ifc_len = sizeof(i_faces); + + if (ioctl(sock, SIOCGIFCONF, &ifconf) < 0) { + close(sock); + return; + } + + /* We copy these to the static variables broadcast_* only at the end of fetch_broadcast_info(). + * The intention is to ensure that even if multiple threads enter fetch_broadcast_info() concurrently, only valid + * interfaces will be set to be broadcast to. + * */ + int count = 0; + IP_Port ip_ports[MAX_INTERFACES]; + + /* ifconf.ifc_len is set by the ioctl() to the actual length used; + * on usage of the complete array the call should be repeated with + * a larger array, not done (640kB and 16 interfaces shall be + * enough, for everybody!) + */ + int i, n = ifconf.ifc_len / sizeof(struct ifreq); + + for (i = 0; i < n; i++) { + /* there are interfaces with are incapable of broadcast */ + if (ioctl(sock, SIOCGIFBRDADDR, &i_faces[i]) < 0) { + continue; + } + + /* moot check: only TOX_AF_INET returned (backwards compat.) */ + if (i_faces[i].ifr_broadaddr.sa_family != TOX_AF_INET) { + continue; + } + + struct sockaddr_in *sock4 = (struct sockaddr_in *)&i_faces[i].ifr_broadaddr; + + if (count >= MAX_INTERFACES) { + break; + } + + IP_Port *ip_port = &ip_ports[count]; + ip_port->ip.family = TOX_AF_INET; + ip_port->ip.ip4.uint32 = sock4->sin_addr.s_addr; + + if (ip_port->ip.ip4.uint32 == 0) { + continue; + } + + ip_port->port = port; + count++; + } + + close(sock); + + broadcast_count = count; + + for (uint32_t i = 0; i < count; i++) { + broadcast_ip_ports[i] = ip_ports[i]; + } +} + +#else // TODO(irungentoo): Other platforms? + +static void fetch_broadcast_info(uint16_t port) +{ + broadcast_count = 0; +} + +#endif +/* Send packet to all IPv4 broadcast addresses + * + * return 1 if sent to at least one broadcast target. + * return 0 on failure to find any valid broadcast target. + */ +static uint32_t send_broadcasts(Networking_Core *net, uint16_t port, const uint8_t *data, uint16_t length) +{ + /* fetch only once? on every packet? every X seconds? + * old: every packet, new: once */ + if (broadcast_count < 0) { + fetch_broadcast_info(port); + } + + if (!broadcast_count) { + return 0; + } + + int i; + + for (i = 0; i < broadcast_count; i++) { + sendpacket(net, broadcast_ip_ports[i], data, length); + } + + return 1; +} + +/* Return the broadcast ip. */ +static IP broadcast_ip(Family family_socket, Family family_broadcast) +{ + IP ip; + ip_reset(&ip); + + if (family_socket == TOX_AF_INET6) { + if (family_broadcast == TOX_AF_INET6) { + ip.family = TOX_AF_INET6; + /* FF02::1 is - according to RFC 4291 - multicast all-nodes link-local */ + /* FE80::*: MUST be exact, for that we would need to look over all + * interfaces and check in which status they are */ + ip.ip6.uint8[ 0] = 0xFF; + ip.ip6.uint8[ 1] = 0x02; + ip.ip6.uint8[15] = 0x01; + } else if (family_broadcast == TOX_AF_INET) { + ip.family = TOX_AF_INET6; + ip.ip6 = IP6_BROADCAST; + } + } else if (family_socket == TOX_AF_INET) { + if (family_broadcast == TOX_AF_INET) { + ip.family = TOX_AF_INET; + ip.ip4 = IP4_BROADCAST; + } + } + + return ip; +} + +/* Is IP a local ip or not. */ +bool Local_ip(IP ip) +{ + if (ip.family == TOX_AF_INET) { + IP4 ip4 = ip.ip4; + + /* Loopback. */ + if (ip4.uint8[0] == 127) { + return 1; + } + } else { + /* embedded IPv4-in-IPv6 */ + if (IPV6_IPV4_IN_V6(ip.ip6)) { + IP ip4; + ip4.family = TOX_AF_INET; + ip4.ip4.uint32 = ip.ip6.uint32[3]; + return Local_ip(ip4); + } + + /* localhost in IPv6 (::1) */ + if (ip.ip6.uint64[0] == 0 && ip.ip6.uint32[2] == 0 && ip.ip6.uint32[3] == net_htonl(1)) { + return 1; + } + } + + return 0; +} + +/* return 0 if ip is a LAN ip. + * return -1 if it is not. + */ +int LAN_ip(IP ip) +{ + if (Local_ip(ip)) { + return 0; + } + + if (ip.family == TOX_AF_INET) { + IP4 ip4 = ip.ip4; + + /* 10.0.0.0 to 10.255.255.255 range. */ + if (ip4.uint8[0] == 10) { + return 0; + } + + /* 172.16.0.0 to 172.31.255.255 range. */ + if (ip4.uint8[0] == 172 && ip4.uint8[1] >= 16 && ip4.uint8[1] <= 31) { + return 0; + } + + /* 192.168.0.0 to 192.168.255.255 range. */ + if (ip4.uint8[0] == 192 && ip4.uint8[1] == 168) { + return 0; + } + + /* 169.254.1.0 to 169.254.254.255 range. */ + if (ip4.uint8[0] == 169 && ip4.uint8[1] == 254 && ip4.uint8[2] != 0 + && 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 == TOX_AF_INET6) { + + /* autogenerated for each interface: FE80::* (up to FEBF::*) + FF02::1 is - according to RFC 4291 - multicast all-nodes link-local */ + if (((ip.ip6.uint8[0] == 0xFF) && (ip.ip6.uint8[1] < 3) && (ip.ip6.uint8[15] == 1)) || + ((ip.ip6.uint8[0] == 0xFE) && ((ip.ip6.uint8[1] & 0xC0) == 0x80))) { + return 0; + } + + /* embedded IPv4-in-IPv6 */ + if (IPV6_IPV4_IN_V6(ip.ip6)) { + IP ip4; + ip4.family = TOX_AF_INET; + ip4.ip4.uint32 = ip.ip6.uint32[3]; + return LAN_ip(ip4); + } + } + + return -1; +} + +static int handle_LANdiscovery(void *object, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) +{ + DHT *dht = (DHT *)object; + + if (LAN_ip(source.ip) == -1) { + return 1; + } + + if (length != CRYPTO_PUBLIC_KEY_SIZE + 1) { + return 1; + } + + char ip_str[IP_NTOA_LEN] = { 0 }; + ip_ntoa(&source.ip, ip_str, sizeof(ip_str)); + LOGGER_INFO(dht->log, "Found node in LAN: %s", ip_str); + + DHT_bootstrap(dht, source, packet + 1); + return 0; +} + + +int send_LANdiscovery(uint16_t port, DHT *dht) +{ + uint8_t data[CRYPTO_PUBLIC_KEY_SIZE + 1]; + data[0] = NET_PACKET_LAN_DISCOVERY; + id_copy(data + 1, dht->self_public_key); + + send_broadcasts(dht->net, port, data, 1 + CRYPTO_PUBLIC_KEY_SIZE); + + int res = -1; + IP_Port ip_port; + ip_port.port = port; + + /* IPv6 multicast */ + if (dht->net->family == TOX_AF_INET6) { + ip_port.ip = broadcast_ip(TOX_AF_INET6, TOX_AF_INET6); + + if (ip_isset(&ip_port.ip)) { + if (sendpacket(dht->net, ip_port, data, 1 + CRYPTO_PUBLIC_KEY_SIZE) > 0) { + res = 1; + } + } + } + + /* IPv4 broadcast (has to be IPv4-in-IPv6 mapping if socket is TOX_AF_INET6 */ + ip_port.ip = broadcast_ip(dht->net->family, TOX_AF_INET); + + if (ip_isset(&ip_port.ip)) { + if (sendpacket(dht->net, ip_port, data, 1 + CRYPTO_PUBLIC_KEY_SIZE)) { + res = 1; + } + } + + return res; +} + + +void LANdiscovery_init(DHT *dht) +{ + networking_registerhandler(dht->net, NET_PACKET_LAN_DISCOVERY, &handle_LANdiscovery, dht); +} + +void LANdiscovery_kill(DHT *dht) +{ + networking_registerhandler(dht->net, NET_PACKET_LAN_DISCOVERY, NULL, NULL); +} diff --git a/libs/libtox/src/toxcore/LAN_discovery.h b/libs/libtox/src/toxcore/LAN_discovery.h new file mode 100644 index 0000000000..753de5249f --- /dev/null +++ b/libs/libtox/src/toxcore/LAN_discovery.h @@ -0,0 +1,52 @@ +/* + * LAN discovery implementation. + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2013 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifndef LAN_DISCOVERY_H +#define LAN_DISCOVERY_H + +#include "DHT.h" + +/* Interval in seconds between LAN discovery packet sending. */ +#define LAN_DISCOVERY_INTERVAL 10 + +/* Send a LAN discovery pcaket to the broadcast address with port port. */ +int send_LANdiscovery(uint16_t port, DHT *dht); + +/* Sets up packet handlers. */ +void LANdiscovery_init(DHT *dht); + +/* Clear packet handlers. */ +void LANdiscovery_kill(DHT *dht); + +/* Is IP a local ip or not. */ +bool Local_ip(IP ip); + +/* checks if a given IP isn't routable + * + * return 0 if ip is a LAN ip. + * return -1 if it is not. + */ +int LAN_ip(IP ip); + + +#endif diff --git a/libs/libtox/src/toxcore/Messenger.c b/libs/libtox/src/toxcore/Messenger.c new file mode 100644 index 0000000000..4dd7d0c4a4 --- /dev/null +++ b/libs/libtox/src/toxcore/Messenger.c @@ -0,0 +1,3155 @@ +/* + * An implementation of a simple text chat only messenger on the tox network core. + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2013 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "Messenger.h" + +#include "logger.h" +#include "network.h" +#include "util.h" + +#include <assert.h> + +static void set_friend_status(Messenger *m, int32_t friendnumber, uint8_t status, void *userdata); +static int write_cryptpacket_id(const Messenger *m, int32_t friendnumber, uint8_t packet_id, const uint8_t *data, + uint32_t length, uint8_t congestion_control); + +// friend_not_valid determines if the friendnumber passed is valid in the Messenger object +static uint8_t friend_not_valid(const Messenger *m, int32_t friendnumber) +{ + if ((unsigned int)friendnumber < m->numfriends) { + if (m->friendlist[friendnumber].status != 0) { + return 0; + } + } + + return 1; +} + +/* Set the size of the friend list to numfriends. + * + * return -1 if realloc fails. + */ +static int realloc_friendlist(Messenger *m, uint32_t num) +{ + if (num == 0) { + free(m->friendlist); + m->friendlist = NULL; + return 0; + } + + Friend *newfriendlist = (Friend *)realloc(m->friendlist, num * sizeof(Friend)); + + if (newfriendlist == NULL) { + return -1; + } + + m->friendlist = newfriendlist; + return 0; +} + +/* return the friend id associated to that public key. + * return -1 if no such friend. + */ +int32_t getfriend_id(const Messenger *m, const uint8_t *real_pk) +{ + uint32_t i; + + for (i = 0; i < m->numfriends; ++i) { + if (m->friendlist[i].status > 0) { + if (id_equal(real_pk, m->friendlist[i].real_pk)) { + return i; + } + } + } + + return -1; +} + +/* Copies the public key associated to that friend id into real_pk buffer. + * Make sure that real_pk is of size CRYPTO_PUBLIC_KEY_SIZE. + * + * return 0 if success. + * return -1 if failure. + */ +int get_real_pk(const Messenger *m, int32_t friendnumber, uint8_t *real_pk) +{ + if (friend_not_valid(m, friendnumber)) { + return -1; + } + + memcpy(real_pk, m->friendlist[friendnumber].real_pk, CRYPTO_PUBLIC_KEY_SIZE); + return 0; +} + +/* return friend connection id on success. + * return -1 if failure. + */ +int getfriendcon_id(const Messenger *m, int32_t friendnumber) +{ + if (friend_not_valid(m, friendnumber)) { + return -1; + } + + return m->friendlist[friendnumber].friendcon_id; +} + +/* + * return a uint16_t that represents the checksum of address of length len. + */ +static uint16_t address_checksum(const uint8_t *address, uint32_t len) +{ + uint8_t checksum[2] = {0}; + uint16_t check; + uint32_t i; + + for (i = 0; i < len; ++i) { + checksum[i % 2] ^= address[i]; + } + + memcpy(&check, checksum, sizeof(check)); + return check; +} + +/* Format: [real_pk (32 bytes)][nospam number (4 bytes)][checksum (2 bytes)] + * + * return FRIEND_ADDRESS_SIZE byte address to give to others. + */ +void getaddress(const Messenger *m, uint8_t *address) +{ + id_copy(address, m->net_crypto->self_public_key); + uint32_t nospam = get_nospam(&(m->fr)); + memcpy(address + CRYPTO_PUBLIC_KEY_SIZE, &nospam, sizeof(nospam)); + uint16_t checksum = address_checksum(address, FRIEND_ADDRESS_SIZE - sizeof(checksum)); + memcpy(address + CRYPTO_PUBLIC_KEY_SIZE + sizeof(nospam), &checksum, sizeof(checksum)); +} + +static int send_online_packet(Messenger *m, int32_t friendnumber) +{ + if (friend_not_valid(m, friendnumber)) { + return 0; + } + + uint8_t packet = PACKET_ID_ONLINE; + return write_cryptpacket(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c, + m->friendlist[friendnumber].friendcon_id), &packet, sizeof(packet), 0) != -1; +} + +static int send_offline_packet(Messenger *m, int friendcon_id) +{ + uint8_t packet = PACKET_ID_OFFLINE; + return write_cryptpacket(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c, friendcon_id), &packet, + sizeof(packet), 0) != -1; +} + +static int m_handle_status(void *object, int i, uint8_t status, void *userdata); +static int m_handle_packet(void *object, int i, const uint8_t *temp, uint16_t len, void *userdata); +static int m_handle_custom_lossy_packet(void *object, int friend_num, const uint8_t *packet, uint16_t length, + void *userdata); + +static int32_t init_new_friend(Messenger *m, const uint8_t *real_pk, uint8_t status) +{ + /* Resize the friend list if necessary. */ + if (realloc_friendlist(m, m->numfriends + 1) != 0) { + return FAERR_NOMEM; + } + + memset(&(m->friendlist[m->numfriends]), 0, sizeof(Friend)); + + int friendcon_id = new_friend_connection(m->fr_c, real_pk); + + if (friendcon_id == -1) { + return FAERR_NOMEM; + } + + uint32_t i; + + for (i = 0; i <= m->numfriends; ++i) { + if (m->friendlist[i].status == NOFRIEND) { + m->friendlist[i].status = status; + m->friendlist[i].friendcon_id = friendcon_id; + m->friendlist[i].friendrequest_lastsent = 0; + id_copy(m->friendlist[i].real_pk, real_pk); + m->friendlist[i].statusmessage_length = 0; + m->friendlist[i].userstatus = USERSTATUS_NONE; + m->friendlist[i].is_typing = 0; + m->friendlist[i].message_id = 0; + friend_connection_callbacks(m->fr_c, friendcon_id, MESSENGER_CALLBACK_INDEX, &m_handle_status, &m_handle_packet, + &m_handle_custom_lossy_packet, m, i); + + if (m->numfriends == i) { + ++m->numfriends; + } + + if (friend_con_connected(m->fr_c, friendcon_id) == FRIENDCONN_STATUS_CONNECTED) { + send_online_packet(m, i); + } + + return i; + } + } + + return FAERR_NOMEM; +} + +/* + * Add a friend. + * Set the data that will be sent along with friend request. + * Address is the address of the friend (returned by getaddress of the friend you wish to add) it must be FRIEND_ADDRESS_SIZE bytes. + * data is the data and length is the length. + * + * return the friend number if success. + * return FA_TOOLONG if message length is too long. + * return FAERR_NOMESSAGE if no message (message length must be >= 1 byte). + * return FAERR_OWNKEY if user's own key. + * return FAERR_ALREADYSENT if friend request already sent or already a friend. + * return FAERR_BADCHECKSUM if bad checksum in address. + * return FAERR_SETNEWNOSPAM if the friend was already there but the nospam was different. + * (the nospam for that friend was set to the new one). + * return FAERR_NOMEM if increasing the friend list size fails. + */ +int32_t m_addfriend(Messenger *m, const uint8_t *address, const uint8_t *data, uint16_t length) +{ + if (length > MAX_FRIEND_REQUEST_DATA_SIZE) { + return FAERR_TOOLONG; + } + + uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; + id_copy(real_pk, address); + + if (!public_key_valid(real_pk)) { + return FAERR_BADCHECKSUM; + } + + uint16_t check, checksum = address_checksum(address, FRIEND_ADDRESS_SIZE - sizeof(checksum)); + memcpy(&check, address + CRYPTO_PUBLIC_KEY_SIZE + sizeof(uint32_t), sizeof(check)); + + if (check != checksum) { + return FAERR_BADCHECKSUM; + } + + if (length < 1) { + return FAERR_NOMESSAGE; + } + + if (id_equal(real_pk, m->net_crypto->self_public_key)) { + return FAERR_OWNKEY; + } + + int32_t friend_id = getfriend_id(m, real_pk); + + if (friend_id != -1) { + if (m->friendlist[friend_id].status >= FRIEND_CONFIRMED) { + return FAERR_ALREADYSENT; + } + + uint32_t nospam; + memcpy(&nospam, address + CRYPTO_PUBLIC_KEY_SIZE, sizeof(nospam)); + + if (m->friendlist[friend_id].friendrequest_nospam == nospam) { + return FAERR_ALREADYSENT; + } + + m->friendlist[friend_id].friendrequest_nospam = nospam; + return FAERR_SETNEWNOSPAM; + } + + int32_t ret = init_new_friend(m, real_pk, FRIEND_ADDED); + + if (ret < 0) { + return ret; + } + + m->friendlist[ret].friendrequest_timeout = FRIENDREQUEST_TIMEOUT; + memcpy(m->friendlist[ret].info, data, length); + m->friendlist[ret].info_size = length; + memcpy(&(m->friendlist[ret].friendrequest_nospam), address + CRYPTO_PUBLIC_KEY_SIZE, sizeof(uint32_t)); + + return ret; +} + +int32_t m_addfriend_norequest(Messenger *m, const uint8_t *real_pk) +{ + if (getfriend_id(m, real_pk) != -1) { + return FAERR_ALREADYSENT; + } + + if (!public_key_valid(real_pk)) { + return FAERR_BADCHECKSUM; + } + + if (id_equal(real_pk, m->net_crypto->self_public_key)) { + return FAERR_OWNKEY; + } + + return init_new_friend(m, real_pk, FRIEND_CONFIRMED); +} + +static int clear_receipts(Messenger *m, int32_t friendnumber) +{ + if (friend_not_valid(m, friendnumber)) { + return -1; + } + + struct Receipts *receipts = m->friendlist[friendnumber].receipts_start; + + while (receipts) { + struct Receipts *temp_r = receipts->next; + free(receipts); + receipts = temp_r; + } + + m->friendlist[friendnumber].receipts_start = NULL; + m->friendlist[friendnumber].receipts_end = NULL; + return 0; +} + +static int add_receipt(Messenger *m, int32_t friendnumber, uint32_t packet_num, uint32_t msg_id) +{ + if (friend_not_valid(m, friendnumber)) { + return -1; + } + + struct Receipts *new_receipts = (struct Receipts *)calloc(1, sizeof(struct Receipts)); + + if (!new_receipts) { + return -1; + } + + new_receipts->packet_num = packet_num; + new_receipts->msg_id = msg_id; + + if (!m->friendlist[friendnumber].receipts_start) { + m->friendlist[friendnumber].receipts_start = new_receipts; + } else { + m->friendlist[friendnumber].receipts_end->next = new_receipts; + } + + m->friendlist[friendnumber].receipts_end = new_receipts; + new_receipts->next = NULL; + return 0; +} +/* + * return -1 on failure. + * return 0 if packet was received. + */ +static int friend_received_packet(const Messenger *m, int32_t friendnumber, uint32_t number) +{ + if (friend_not_valid(m, friendnumber)) { + return -1; + } + + return cryptpacket_received(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c, + m->friendlist[friendnumber].friendcon_id), number); +} + +static int do_receipts(Messenger *m, int32_t friendnumber, void *userdata) +{ + if (friend_not_valid(m, friendnumber)) { + return -1; + } + + struct Receipts *receipts = m->friendlist[friendnumber].receipts_start; + + while (receipts) { + struct Receipts *temp_r = receipts->next; + + if (friend_received_packet(m, friendnumber, receipts->packet_num) == -1) { + break; + } + + if (m->read_receipt) { + (*m->read_receipt)(m, friendnumber, receipts->msg_id, userdata); + } + + free(receipts); + m->friendlist[friendnumber].receipts_start = temp_r; + receipts = temp_r; + } + + if (!m->friendlist[friendnumber].receipts_start) { + m->friendlist[friendnumber].receipts_end = NULL; + } + + return 0; +} + +/* Remove a friend. + * + * return 0 if success. + * return -1 if failure. + */ +int m_delfriend(Messenger *m, int32_t friendnumber) +{ + if (friend_not_valid(m, friendnumber)) { + return -1; + } + + if (m->friend_connectionstatuschange_internal) { + m->friend_connectionstatuschange_internal(m, friendnumber, 0, m->friend_connectionstatuschange_internal_userdata); + } + + clear_receipts(m, friendnumber); + remove_request_received(&(m->fr), m->friendlist[friendnumber].real_pk); + friend_connection_callbacks(m->fr_c, m->friendlist[friendnumber].friendcon_id, MESSENGER_CALLBACK_INDEX, 0, 0, 0, 0, 0); + + if (friend_con_connected(m->fr_c, m->friendlist[friendnumber].friendcon_id) == FRIENDCONN_STATUS_CONNECTED) { + send_offline_packet(m, m->friendlist[friendnumber].friendcon_id); + } + + kill_friend_connection(m->fr_c, m->friendlist[friendnumber].friendcon_id); + memset(&(m->friendlist[friendnumber]), 0, sizeof(Friend)); + uint32_t i; + + for (i = m->numfriends; i != 0; --i) { + if (m->friendlist[i - 1].status != NOFRIEND) { + break; + } + } + + m->numfriends = i; + + if (realloc_friendlist(m, m->numfriends) != 0) { + return FAERR_NOMEM; + } + + return 0; +} + +int m_get_friend_connectionstatus(const Messenger *m, int32_t friendnumber) +{ + if (friend_not_valid(m, friendnumber)) { + return -1; + } + + if (m->friendlist[friendnumber].status == FRIEND_ONLINE) { + bool direct_connected = 0; + unsigned int num_online_relays = 0; + int crypt_conn_id = friend_connection_crypt_connection_id(m->fr_c, m->friendlist[friendnumber].friendcon_id); + crypto_connection_status(m->net_crypto, crypt_conn_id, &direct_connected, &num_online_relays); + + if (direct_connected) { + return CONNECTION_UDP; + } + + if (num_online_relays) { + return CONNECTION_TCP; + } + + return CONNECTION_UNKNOWN; + } + + return CONNECTION_NONE; +} + +int m_friend_exists(const Messenger *m, int32_t friendnumber) +{ + if (friend_not_valid(m, friendnumber)) { + return 0; + } + + return 1; +} + +/* Send a message of type. + * + * return -1 if friend not valid. + * return -2 if too large. + * return -3 if friend not online. + * return -4 if send failed (because queue is full). + * return -5 if bad type. + * return 0 if success. + */ +int m_send_message_generic(Messenger *m, int32_t friendnumber, uint8_t type, const uint8_t *message, uint32_t length, + uint32_t *message_id) +{ + if (type > MESSAGE_ACTION) { + return -5; + } + + if (friend_not_valid(m, friendnumber)) { + return -1; + } + + if (length >= MAX_CRYPTO_DATA_SIZE) { + return -2; + } + + if (m->friendlist[friendnumber].status != FRIEND_ONLINE) { + return -3; + } + + VLA(uint8_t, packet, length + 1); + packet[0] = type + PACKET_ID_MESSAGE; + + if (length != 0) { + memcpy(packet + 1, message, length); + } + + int64_t packet_num = write_cryptpacket(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c, + m->friendlist[friendnumber].friendcon_id), packet, length + 1, 0); + + if (packet_num == -1) { + return -4; + } + + uint32_t msg_id = ++m->friendlist[friendnumber].message_id; + + add_receipt(m, friendnumber, packet_num, msg_id); + + if (message_id) { + *message_id = msg_id; + } + + return 0; +} + +/* Send a name packet to friendnumber. + * length is the length with the NULL terminator. + */ +static int m_sendname(const Messenger *m, int32_t friendnumber, const uint8_t *name, uint16_t length) +{ + if (length > MAX_NAME_LENGTH) { + return 0; + } + + return write_cryptpacket_id(m, friendnumber, PACKET_ID_NICKNAME, name, length, 0); +} + +/* Set the name and name_length of a friend. + * + * return 0 if success. + * return -1 if failure. + */ +int setfriendname(Messenger *m, int32_t friendnumber, const uint8_t *name, uint16_t length) +{ + if (friend_not_valid(m, friendnumber)) { + return -1; + } + + if (length > MAX_NAME_LENGTH || length == 0) { + return -1; + } + + m->friendlist[friendnumber].name_length = length; + memcpy(m->friendlist[friendnumber].name, name, length); + return 0; +} + +/* Set our nickname + * name must be a string of maximum MAX_NAME_LENGTH length. + * length must be at least 1 byte. + * length is the length of name with the NULL terminator. + * + * return 0 if success. + * return -1 if failure. + */ +int setname(Messenger *m, const uint8_t *name, uint16_t length) +{ + if (length > MAX_NAME_LENGTH) { + return -1; + } + + if (m->name_length == length && (length == 0 || memcmp(name, m->name, length) == 0)) { + return 0; + } + + if (length) { + memcpy(m->name, name, length); + } + + m->name_length = length; + uint32_t i; + + for (i = 0; i < m->numfriends; ++i) { + m->friendlist[i].name_sent = 0; + } + + return 0; +} + +/* Get our nickname and put it in name. + * name needs to be a valid memory location with a size of at least MAX_NAME_LENGTH bytes. + * + * return the length of the name. + */ +uint16_t getself_name(const Messenger *m, uint8_t *name) +{ + if (name == NULL) { + return 0; + } + + memcpy(name, m->name, m->name_length); + + return m->name_length; +} + +/* 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 bytes. + * + * return length of name if success. + * return -1 if failure. + */ +int getname(const Messenger *m, int32_t friendnumber, uint8_t *name) +{ + if (friend_not_valid(m, friendnumber)) { + return -1; + } + + memcpy(name, m->friendlist[friendnumber].name, m->friendlist[friendnumber].name_length); + return m->friendlist[friendnumber].name_length; +} + +int m_get_name_size(const Messenger *m, int32_t friendnumber) +{ + if (friend_not_valid(m, friendnumber)) { + return -1; + } + + return m->friendlist[friendnumber].name_length; +} + +int m_get_self_name_size(const Messenger *m) +{ + return m->name_length; +} + +int m_set_statusmessage(Messenger *m, const uint8_t *status, uint16_t length) +{ + if (length > MAX_STATUSMESSAGE_LENGTH) { + return -1; + } + + if (m->statusmessage_length == length && (length == 0 || memcmp(m->statusmessage, status, length) == 0)) { + return 0; + } + + if (length) { + memcpy(m->statusmessage, status, length); + } + + m->statusmessage_length = length; + + uint32_t i; + + for (i = 0; i < m->numfriends; ++i) { + m->friendlist[i].statusmessage_sent = 0; + } + + return 0; +} + +int m_set_userstatus(Messenger *m, uint8_t status) +{ + if (status >= USERSTATUS_INVALID) { + return -1; + } + + if (m->userstatus == status) { + return 0; + } + + m->userstatus = (USERSTATUS)status; + uint32_t i; + + for (i = 0; i < m->numfriends; ++i) { + m->friendlist[i].userstatus_sent = 0; + } + + return 0; +} + +/* return the size of friendnumber's user status. + * Guaranteed to be at most MAX_STATUSMESSAGE_LENGTH. + */ +int m_get_statusmessage_size(const Messenger *m, int32_t friendnumber) +{ + if (friend_not_valid(m, friendnumber)) { + return -1; + } + + return m->friendlist[friendnumber].statusmessage_length; +} + +/* Copy the user status of friendnumber into buf, truncating if needed to maxlen + * bytes, use m_get_statusmessage_size to find out how much you need to allocate. + */ +int m_copy_statusmessage(const Messenger *m, int32_t friendnumber, uint8_t *buf, uint32_t maxlen) +{ + if (friend_not_valid(m, friendnumber)) { + return -1; + } + + int msglen = MIN(maxlen, m->friendlist[friendnumber].statusmessage_length); + + memcpy(buf, m->friendlist[friendnumber].statusmessage, msglen); + memset(buf + msglen, 0, maxlen - msglen); + return msglen; +} + +/* return the size of friendnumber's user status. + * Guaranteed to be at most MAX_STATUSMESSAGE_LENGTH. + */ +int m_get_self_statusmessage_size(const Messenger *m) +{ + return m->statusmessage_length; +} + +int m_copy_self_statusmessage(const Messenger *m, uint8_t *buf) +{ + memcpy(buf, m->statusmessage, m->statusmessage_length); + return m->statusmessage_length; +} + +uint8_t m_get_userstatus(const Messenger *m, int32_t friendnumber) +{ + if (friend_not_valid(m, friendnumber)) { + return USERSTATUS_INVALID; + } + + uint8_t status = m->friendlist[friendnumber].userstatus; + + if (status >= USERSTATUS_INVALID) { + status = USERSTATUS_NONE; + } + + return status; +} + +uint8_t m_get_self_userstatus(const Messenger *m) +{ + return m->userstatus; +} + +uint64_t m_get_last_online(const Messenger *m, int32_t friendnumber) +{ + if (friend_not_valid(m, friendnumber)) { + return UINT64_MAX; + } + + return m->friendlist[friendnumber].last_seen_time; +} + +int m_set_usertyping(Messenger *m, int32_t friendnumber, uint8_t is_typing) +{ + if (is_typing != 0 && is_typing != 1) { + return -1; + } + + if (friend_not_valid(m, friendnumber)) { + return -1; + } + + if (m->friendlist[friendnumber].user_istyping == is_typing) { + return 0; + } + + m->friendlist[friendnumber].user_istyping = is_typing; + m->friendlist[friendnumber].user_istyping_sent = 0; + + return 0; +} + +int m_get_istyping(const Messenger *m, int32_t friendnumber) +{ + if (friend_not_valid(m, friendnumber)) { + return -1; + } + + return m->friendlist[friendnumber].is_typing; +} + +static int send_statusmessage(const Messenger *m, int32_t friendnumber, const uint8_t *status, uint16_t length) +{ + return write_cryptpacket_id(m, friendnumber, PACKET_ID_STATUSMESSAGE, status, length, 0); +} + +static int send_userstatus(const Messenger *m, int32_t friendnumber, uint8_t status) +{ + return write_cryptpacket_id(m, friendnumber, PACKET_ID_USERSTATUS, &status, sizeof(status), 0); +} + +static int send_user_istyping(const Messenger *m, int32_t friendnumber, uint8_t is_typing) +{ + uint8_t typing = is_typing; + return write_cryptpacket_id(m, friendnumber, PACKET_ID_TYPING, &typing, sizeof(typing), 0); +} + +static int set_friend_statusmessage(const Messenger *m, int32_t friendnumber, const uint8_t *status, uint16_t length) +{ + if (friend_not_valid(m, friendnumber)) { + return -1; + } + + if (length > MAX_STATUSMESSAGE_LENGTH) { + return -1; + } + + if (length) { + memcpy(m->friendlist[friendnumber].statusmessage, status, length); + } + + m->friendlist[friendnumber].statusmessage_length = length; + return 0; +} + +static void set_friend_userstatus(const Messenger *m, int32_t friendnumber, uint8_t status) +{ + m->friendlist[friendnumber].userstatus = (USERSTATUS)status; +} + +static void set_friend_typing(const Messenger *m, int32_t friendnumber, uint8_t is_typing) +{ + m->friendlist[friendnumber].is_typing = is_typing; +} + +void m_callback_log(Messenger *m, logger_cb *function, void *context, void *userdata) +{ + logger_callback_log(m->log, function, context, userdata); +} + +/* Set the function that will be executed when a friend request is received. */ +void m_callback_friendrequest(Messenger *m, void (*function)(Messenger *m, const uint8_t *, const uint8_t *, size_t, + void *)) +{ + callback_friendrequest(&(m->fr), (void (*)(void *, const uint8_t *, const uint8_t *, size_t, void *))function, m); +} + +/* Set the function that will be executed when a message from a friend is received. */ +void m_callback_friendmessage(Messenger *m, void (*function)(Messenger *m, uint32_t, unsigned int, const uint8_t *, + size_t, void *)) +{ + m->friend_message = function; +} + +void m_callback_namechange(Messenger *m, void (*function)(Messenger *m, uint32_t, const uint8_t *, size_t, void *)) +{ + m->friend_namechange = function; +} + +void m_callback_statusmessage(Messenger *m, void (*function)(Messenger *m, uint32_t, const uint8_t *, size_t, void *)) +{ + m->friend_statusmessagechange = function; +} + +void m_callback_userstatus(Messenger *m, void (*function)(Messenger *m, uint32_t, unsigned int, void *)) +{ + m->friend_userstatuschange = function; +} + +void m_callback_typingchange(Messenger *m, void(*function)(Messenger *m, uint32_t, bool, void *)) +{ + m->friend_typingchange = function; +} + +void m_callback_read_receipt(Messenger *m, void (*function)(Messenger *m, uint32_t, uint32_t, void *)) +{ + m->read_receipt = function; +} + +void m_callback_connectionstatus(Messenger *m, void (*function)(Messenger *m, uint32_t, unsigned int, void *)) +{ + m->friend_connectionstatuschange = function; +} + +void m_callback_core_connection(Messenger *m, void (*function)(Messenger *m, unsigned int, void *)) +{ + m->core_connection_change = function; +} + +void m_callback_connectionstatus_internal_av(Messenger *m, void (*function)(Messenger *m, uint32_t, uint8_t, void *), + void *userdata) +{ + m->friend_connectionstatuschange_internal = function; + m->friend_connectionstatuschange_internal_userdata = userdata; +} + +static void check_friend_tcp_udp(Messenger *m, int32_t friendnumber, void *userdata) +{ + int last_connection_udp_tcp = m->friendlist[friendnumber].last_connection_udp_tcp; + + int ret = m_get_friend_connectionstatus(m, friendnumber); + + if (ret == -1) { + return; + } + + if (ret == CONNECTION_UNKNOWN) { + if (last_connection_udp_tcp == CONNECTION_UDP) { + return; + } + + ret = CONNECTION_TCP; + } + + if (last_connection_udp_tcp != ret) { + if (m->friend_connectionstatuschange) { + m->friend_connectionstatuschange(m, friendnumber, ret, userdata); + } + } + + m->friendlist[friendnumber].last_connection_udp_tcp = ret; +} + +static void break_files(const Messenger *m, int32_t friendnumber); +static void check_friend_connectionstatus(Messenger *m, int32_t friendnumber, uint8_t status, void *userdata) +{ + if (status == NOFRIEND) { + return; + } + + const uint8_t was_online = m->friendlist[friendnumber].status == FRIEND_ONLINE; + const uint8_t is_online = status == FRIEND_ONLINE; + + if (is_online != was_online) { + if (was_online) { + break_files(m, friendnumber); + clear_receipts(m, friendnumber); + } else { + m->friendlist[friendnumber].name_sent = 0; + m->friendlist[friendnumber].userstatus_sent = 0; + m->friendlist[friendnumber].statusmessage_sent = 0; + m->friendlist[friendnumber].user_istyping_sent = 0; + } + + m->friendlist[friendnumber].status = status; + + check_friend_tcp_udp(m, friendnumber, userdata); + + if (m->friend_connectionstatuschange_internal) { + m->friend_connectionstatuschange_internal(m, friendnumber, is_online, + m->friend_connectionstatuschange_internal_userdata); + } + } +} + +void set_friend_status(Messenger *m, int32_t friendnumber, uint8_t status, void *userdata) +{ + check_friend_connectionstatus(m, friendnumber, status, userdata); + m->friendlist[friendnumber].status = status; +} + +static int write_cryptpacket_id(const Messenger *m, int32_t friendnumber, uint8_t packet_id, const uint8_t *data, + uint32_t length, uint8_t congestion_control) +{ + if (friend_not_valid(m, friendnumber)) { + return 0; + } + + if (length >= MAX_CRYPTO_DATA_SIZE || m->friendlist[friendnumber].status != FRIEND_ONLINE) { + return 0; + } + + VLA(uint8_t, packet, length + 1); + packet[0] = packet_id; + + if (length != 0) { + memcpy(packet + 1, data, length); + } + + return write_cryptpacket(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c, + m->friendlist[friendnumber].friendcon_id), packet, length + 1, congestion_control) != -1; +} + +/**********CONFERENCES************/ + + +/* Set the callback for conference invites. + * + * Function(Messenger *m, uint32_t friendnumber, uint8_t *data, uint16_t length, void *userdata) + */ +void m_callback_conference_invite(Messenger *m, void (*function)(Messenger *m, uint32_t, const uint8_t *, uint16_t, + void *)) +{ + m->conference_invite = function; +} + + +/* Send a conference invite packet. + * + * return 1 on success + * return 0 on failure + */ +int send_conference_invite_packet(const Messenger *m, int32_t friendnumber, const uint8_t *data, uint16_t length) +{ + return write_cryptpacket_id(m, friendnumber, PACKET_ID_INVITE_CONFERENCE, data, length, 0); +} + +/****************FILE SENDING*****************/ + + +/* Set the callback for file send requests. + * + * Function(Tox *tox, uint32_t friendnumber, uint32_t filenumber, uint32_t filetype, uint64_t filesize, uint8_t *filename, size_t filename_length, void *userdata) + */ +void callback_file_sendrequest(Messenger *m, void (*function)(Messenger *m, uint32_t, uint32_t, uint32_t, uint64_t, + const uint8_t *, size_t, void *)) +{ + m->file_sendrequest = function; +} + +/* Set the callback for file control requests. + * + * Function(Tox *tox, uint32_t friendnumber, uint32_t filenumber, unsigned int control_type, void *userdata) + * + */ +void callback_file_control(Messenger *m, void (*function)(Messenger *m, uint32_t, uint32_t, unsigned int, void *)) +{ + m->file_filecontrol = function; +} + +/* Set the callback for file data. + * + * Function(Tox *tox, uint32_t friendnumber, uint32_t filenumber, uint64_t position, uint8_t *data, size_t length, void *userdata) + * + */ +void callback_file_data(Messenger *m, void (*function)(Messenger *m, uint32_t, uint32_t, uint64_t, const uint8_t *, + size_t, void *)) +{ + m->file_filedata = function; +} + +/* Set the callback for file request chunk. + * + * Function(Tox *tox, uint32_t friendnumber, uint32_t filenumber, uint64_t position, size_t length, void *userdata) + * + */ +void callback_file_reqchunk(Messenger *m, void (*function)(Messenger *m, uint32_t, uint32_t, uint64_t, size_t, void *)) +{ + m->file_reqchunk = function; +} + +#define MAX_FILENAME_LENGTH 255 + +/* Copy the file transfer file id to file_id + * + * return 0 on success. + * return -1 if friend not valid. + * return -2 if filenumber not valid + */ +int file_get_id(const Messenger *m, int32_t friendnumber, uint32_t filenumber, uint8_t *file_id) +{ + if (friend_not_valid(m, friendnumber)) { + return -1; + } + + if (m->friendlist[friendnumber].status != FRIEND_ONLINE) { + return -2; + } + + uint32_t temp_filenum; + uint8_t send_receive, file_number; + + if (filenumber >= (1 << 16)) { + send_receive = 1; + temp_filenum = (filenumber >> 16) - 1; + } else { + send_receive = 0; + temp_filenum = filenumber; + } + + if (temp_filenum >= MAX_CONCURRENT_FILE_PIPES) { + return -2; + } + + file_number = temp_filenum; + + struct File_Transfers *ft; + + if (send_receive) { + ft = &m->friendlist[friendnumber].file_receiving[file_number]; + } else { + ft = &m->friendlist[friendnumber].file_sending[file_number]; + } + + if (ft->status == FILESTATUS_NONE) { + return -2; + } + + memcpy(file_id, ft->id, FILE_ID_LENGTH); + return 0; +} + +/* Send a file send request. + * Maximum filename length is 255 bytes. + * return 1 on success + * return 0 on failure + */ +static int file_sendrequest(const Messenger *m, int32_t friendnumber, uint8_t filenumber, uint32_t file_type, + uint64_t filesize, const uint8_t *file_id, const uint8_t *filename, uint16_t filename_length) +{ + if (friend_not_valid(m, friendnumber)) { + return 0; + } + + if (filename_length > MAX_FILENAME_LENGTH) { + return 0; + } + + VLA(uint8_t, packet, 1 + sizeof(file_type) + sizeof(filesize) + FILE_ID_LENGTH + filename_length); + packet[0] = filenumber; + file_type = net_htonl(file_type); + memcpy(packet + 1, &file_type, sizeof(file_type)); + host_to_net((uint8_t *)&filesize, sizeof(filesize)); + memcpy(packet + 1 + sizeof(file_type), &filesize, sizeof(filesize)); + memcpy(packet + 1 + sizeof(file_type) + sizeof(filesize), file_id, FILE_ID_LENGTH); + + if (filename_length) { + memcpy(packet + 1 + sizeof(file_type) + sizeof(filesize) + FILE_ID_LENGTH, filename, filename_length); + } + + return write_cryptpacket_id(m, friendnumber, PACKET_ID_FILE_SENDREQUEST, packet, SIZEOF_VLA(packet), 0); +} + +/* Send a file send request. + * Maximum filename length is 255 bytes. + * return file number on success + * return -1 if friend not found. + * return -2 if filename length invalid. + * return -3 if no more file sending slots left. + * return -4 if could not send packet (friend offline). + * + */ +long int new_filesender(const Messenger *m, int32_t friendnumber, uint32_t file_type, uint64_t filesize, + const uint8_t *file_id, const uint8_t *filename, uint16_t filename_length) +{ + if (friend_not_valid(m, friendnumber)) { + return -1; + } + + if (filename_length > MAX_FILENAME_LENGTH) { + return -2; + } + + uint32_t i; + + for (i = 0; i < MAX_CONCURRENT_FILE_PIPES; ++i) { + if (m->friendlist[friendnumber].file_sending[i].status == FILESTATUS_NONE) { + break; + } + } + + if (i == MAX_CONCURRENT_FILE_PIPES) { + return -3; + } + + if (file_sendrequest(m, friendnumber, i, file_type, filesize, file_id, filename, filename_length) == 0) { + return -4; + } + + struct File_Transfers *ft = &m->friendlist[friendnumber].file_sending[i]; + + ft->status = FILESTATUS_NOT_ACCEPTED; + + ft->size = filesize; + + ft->transferred = 0; + + ft->requested = 0; + + ft->slots_allocated = 0; + + ft->paused = FILE_PAUSE_NOT; + + memcpy(ft->id, file_id, FILE_ID_LENGTH); + + ++m->friendlist[friendnumber].num_sending_files; + + return i; +} + +static int send_file_control_packet(const Messenger *m, int32_t friendnumber, uint8_t send_receive, uint8_t filenumber, + uint8_t control_type, uint8_t *data, uint16_t data_length) +{ + if ((unsigned int)(1 + 3 + data_length) > MAX_CRYPTO_DATA_SIZE) { + return -1; + } + + VLA(uint8_t, packet, 3 + data_length); + + packet[0] = send_receive; + packet[1] = filenumber; + packet[2] = control_type; + + if (data_length) { + memcpy(packet + 3, data, data_length); + } + + return write_cryptpacket_id(m, friendnumber, PACKET_ID_FILE_CONTROL, packet, SIZEOF_VLA(packet), 0); +} + +/* Send a file control request. + * + * return 0 on success + * return -1 if friend not valid. + * return -2 if friend not online. + * return -3 if file number invalid. + * return -4 if file control is bad. + * return -5 if file already paused. + * return -6 if resume file failed because it was only paused by the other. + * return -7 if resume file failed because it wasn't paused. + * return -8 if packet failed to send. + */ +int file_control(const Messenger *m, int32_t friendnumber, uint32_t filenumber, unsigned int control) +{ + if (friend_not_valid(m, friendnumber)) { + return -1; + } + + if (m->friendlist[friendnumber].status != FRIEND_ONLINE) { + return -2; + } + + uint32_t temp_filenum; + uint8_t send_receive, file_number; + + if (filenumber >= (1 << 16)) { + send_receive = 1; + temp_filenum = (filenumber >> 16) - 1; + } else { + send_receive = 0; + temp_filenum = filenumber; + } + + if (temp_filenum >= MAX_CONCURRENT_FILE_PIPES) { + return -3; + } + + file_number = temp_filenum; + + struct File_Transfers *ft; + + if (send_receive) { + ft = &m->friendlist[friendnumber].file_receiving[file_number]; + } else { + ft = &m->friendlist[friendnumber].file_sending[file_number]; + } + + if (ft->status == FILESTATUS_NONE) { + return -3; + } + + if (control > FILECONTROL_KILL) { + return -4; + } + + if (control == FILECONTROL_PAUSE && ((ft->paused & FILE_PAUSE_US) || ft->status != FILESTATUS_TRANSFERRING)) { + return -5; + } + + if (control == FILECONTROL_ACCEPT) { + if (ft->status == FILESTATUS_TRANSFERRING) { + if (!(ft->paused & FILE_PAUSE_US)) { + if (ft->paused & FILE_PAUSE_OTHER) { + return -6; + } + + return -7; + } + } else { + if (ft->status != FILESTATUS_NOT_ACCEPTED) { + return -7; + } + + if (!send_receive) { + return -6; + } + } + } + + if (send_file_control_packet(m, friendnumber, send_receive, file_number, control, 0, 0)) { + if (control == FILECONTROL_KILL) { + ft->status = FILESTATUS_NONE; + + if (send_receive == 0) { + --m->friendlist[friendnumber].num_sending_files; + } + } else if (control == FILECONTROL_PAUSE) { + ft->paused |= FILE_PAUSE_US; + } else if (control == FILECONTROL_ACCEPT) { + ft->status = FILESTATUS_TRANSFERRING; + + if (ft->paused & FILE_PAUSE_US) { + ft->paused ^= FILE_PAUSE_US; + } + } + } else { + return -8; + } + + return 0; +} + +/* Send a seek file control request. + * + * return 0 on success + * return -1 if friend not valid. + * return -2 if friend not online. + * return -3 if file number invalid. + * return -4 if not receiving file. + * return -5 if file status wrong. + * return -6 if position bad. + * return -8 if packet failed to send. + */ +int file_seek(const Messenger *m, int32_t friendnumber, uint32_t filenumber, uint64_t position) +{ + if (friend_not_valid(m, friendnumber)) { + return -1; + } + + if (m->friendlist[friendnumber].status != FRIEND_ONLINE) { + return -2; + } + + if (filenumber < (1 << 16)) { + // Not receiving. + return -4; + } + + uint32_t temp_filenum = (filenumber >> 16) - 1; + + if (temp_filenum >= MAX_CONCURRENT_FILE_PIPES) { + return -3; + } + + assert(temp_filenum <= UINT8_MAX); + uint8_t file_number = temp_filenum; + + // We're always receiving at this point. + struct File_Transfers *ft = &m->friendlist[friendnumber].file_receiving[file_number]; + + if (ft->status == FILESTATUS_NONE) { + return -3; + } + + if (ft->status != FILESTATUS_NOT_ACCEPTED) { + return -5; + } + + if (position >= ft->size) { + return -6; + } + + uint64_t sending_pos = position; + host_to_net((uint8_t *)&sending_pos, sizeof(sending_pos)); + + if (send_file_control_packet(m, friendnumber, 1, file_number, FILECONTROL_SEEK, (uint8_t *)&sending_pos, + sizeof(sending_pos))) { + ft->transferred = position; + } else { + return -8; + } + + return 0; +} + +/* return packet number on success. + * return -1 on failure. + */ +static int64_t send_file_data_packet(const Messenger *m, int32_t friendnumber, uint8_t filenumber, const uint8_t *data, + uint16_t length) +{ + if (friend_not_valid(m, friendnumber)) { + return -1; + } + + VLA(uint8_t, packet, 2 + length); + packet[0] = PACKET_ID_FILE_DATA; + packet[1] = filenumber; + + if (length) { + memcpy(packet + 2, data, length); + } + + return write_cryptpacket(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c, + m->friendlist[friendnumber].friendcon_id), packet, SIZEOF_VLA(packet), 1); +} + +#define MAX_FILE_DATA_SIZE (MAX_CRYPTO_DATA_SIZE - 2) +#define MIN_SLOTS_FREE (CRYPTO_MIN_QUEUE_LENGTH / 4) +/* Send file data. + * + * return 0 on success + * return -1 if friend not valid. + * return -2 if friend not online. + * return -3 if filenumber invalid. + * return -4 if file transfer not transferring. + * return -5 if bad data size. + * return -6 if packet queue full. + * return -7 if wrong position. + */ +int file_data(const Messenger *m, int32_t friendnumber, uint32_t filenumber, uint64_t position, const uint8_t *data, + uint16_t length) +{ + if (friend_not_valid(m, friendnumber)) { + return -1; + } + + if (m->friendlist[friendnumber].status != FRIEND_ONLINE) { + return -2; + } + + if (filenumber >= MAX_CONCURRENT_FILE_PIPES) { + return -3; + } + + struct File_Transfers *ft = &m->friendlist[friendnumber].file_sending[filenumber]; + + if (ft->status != FILESTATUS_TRANSFERRING) { + return -4; + } + + if (length > MAX_FILE_DATA_SIZE) { + return -5; + } + + if (ft->size - ft->transferred < length) { + return -5; + } + + if (ft->size != UINT64_MAX && length != MAX_FILE_DATA_SIZE && (ft->transferred + length) != ft->size) { + return -5; + } + + if (position != ft->transferred || (ft->requested <= position && ft->size != 0)) { + return -7; + } + + /* Prevent file sending from filling up the entire buffer preventing messages from being sent. + * TODO(irungentoo): remove */ + if (crypto_num_free_sendqueue_slots(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c, + m->friendlist[friendnumber].friendcon_id)) < MIN_SLOTS_FREE) { + return -6; + } + + int64_t ret = send_file_data_packet(m, friendnumber, filenumber, data, length); + + if (ret != -1) { + // TODO(irungentoo): record packet ids to check if other received complete file. + ft->transferred += length; + + if (ft->slots_allocated) { + --ft->slots_allocated; + } + + if (length != MAX_FILE_DATA_SIZE || ft->size == ft->transferred) { + ft->status = FILESTATUS_FINISHED; + ft->last_packet_number = ret; + } + + return 0; + } + + return -6; +} + +/* Give the number of bytes left to be sent/received. + * + * send_receive is 0 if we want the sending files, 1 if we want the receiving. + * + * return number of bytes remaining to be sent/received on success + * return 0 on failure + */ +uint64_t file_dataremaining(const Messenger *m, int32_t friendnumber, uint8_t filenumber, uint8_t send_receive) +{ + if (friend_not_valid(m, friendnumber)) { + return 0; + } + + if (send_receive == 0) { + if (m->friendlist[friendnumber].file_sending[filenumber].status == FILESTATUS_NONE) { + return 0; + } + + return m->friendlist[friendnumber].file_sending[filenumber].size - + m->friendlist[friendnumber].file_sending[filenumber].transferred; + } + + if (m->friendlist[friendnumber].file_receiving[filenumber].status == FILESTATUS_NONE) { + return 0; + } + + return m->friendlist[friendnumber].file_receiving[filenumber].size - + m->friendlist[friendnumber].file_receiving[filenumber].transferred; +} + +static void do_reqchunk_filecb(Messenger *m, int32_t friendnumber, void *userdata) +{ + if (!m->friendlist[friendnumber].num_sending_files) { + return; + } + + int free_slots = crypto_num_free_sendqueue_slots(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c, + m->friendlist[friendnumber].friendcon_id)); + + if (free_slots < MIN_SLOTS_FREE) { + free_slots = 0; + } else { + free_slots -= MIN_SLOTS_FREE; + } + + unsigned int i, num = m->friendlist[friendnumber].num_sending_files; + + for (i = 0; i < MAX_CONCURRENT_FILE_PIPES; ++i) { + struct File_Transfers *ft = &m->friendlist[friendnumber].file_sending[i]; + + if (ft->status != FILESTATUS_NONE) { + --num; + + if (ft->status == FILESTATUS_FINISHED) { + /* Check if file was entirely sent. */ + if (friend_received_packet(m, friendnumber, ft->last_packet_number) == 0) { + if (m->file_reqchunk) { + (*m->file_reqchunk)(m, friendnumber, i, ft->transferred, 0, userdata); + } + + ft->status = FILESTATUS_NONE; + --m->friendlist[friendnumber].num_sending_files; + } + } + + /* TODO(irungentoo): if file is too slow, switch to the next. */ + if (ft->slots_allocated > (unsigned int)free_slots) { + free_slots = 0; + } else { + free_slots -= ft->slots_allocated; + } + } + + while (ft->status == FILESTATUS_TRANSFERRING && (ft->paused == FILE_PAUSE_NOT)) { + if (max_speed_reached(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c, + m->friendlist[friendnumber].friendcon_id))) { + free_slots = 0; + } + + if (free_slots == 0) { + break; + } + + uint16_t length = MAX_FILE_DATA_SIZE; + + if (ft->size == 0) { + /* Send 0 data to friend if file is 0 length. */ + file_data(m, friendnumber, i, 0, 0, 0); + break; + } + + if (ft->size == ft->requested) { + break; + } + + if (ft->size - ft->requested < length) { + length = ft->size - ft->requested; + } + + ++ft->slots_allocated; + + uint64_t position = ft->requested; + ft->requested += length; + + if (m->file_reqchunk) { + (*m->file_reqchunk)(m, friendnumber, i, position, length, userdata); + } + + --free_slots; + } + + if (num == 0) { + break; + } + } +} + +/* Run this when the friend disconnects. + * Kill all current file transfers. + */ +static void break_files(const Messenger *m, int32_t friendnumber) +{ + uint32_t i; + + // TODO(irungentoo): Inform the client which file transfers get killed with a callback? + for (i = 0; i < MAX_CONCURRENT_FILE_PIPES; ++i) { + if (m->friendlist[friendnumber].file_sending[i].status != FILESTATUS_NONE) { + m->friendlist[friendnumber].file_sending[i].status = FILESTATUS_NONE; + } + + if (m->friendlist[friendnumber].file_receiving[i].status != FILESTATUS_NONE) { + m->friendlist[friendnumber].file_receiving[i].status = FILESTATUS_NONE; + } + } +} + +static struct File_Transfers *get_file_transfer(uint8_t receive_send, uint8_t filenumber, + uint32_t *real_filenumber, Friend *sender) +{ + struct File_Transfers *ft; + + if (receive_send == 0) { + *real_filenumber = (filenumber + 1) << 16; + ft = &sender->file_receiving[filenumber]; + } else { + *real_filenumber = filenumber; + ft = &sender->file_sending[filenumber]; + } + + if (ft->status == FILESTATUS_NONE) { + return NULL; + } + + return ft; +} + +/* return -1 on failure, 0 on success. + */ +static int handle_filecontrol(Messenger *m, int32_t friendnumber, uint8_t receive_send, uint8_t filenumber, + uint8_t control_type, const uint8_t *data, uint16_t length, void *userdata) +{ + if (receive_send > 1) { + LOGGER_DEBUG(m->log, "file control (friend %d, file %d): receive_send value is invalid (should be 0 or 1): %d", + friendnumber, filenumber, receive_send); + return -1; + } + + uint32_t real_filenumber; + struct File_Transfers *ft = get_file_transfer(receive_send, filenumber, &real_filenumber, &m->friendlist[friendnumber]); + + if (ft == NULL) { + LOGGER_DEBUG(m->log, "file control (friend %d, file %d): file transfer does not exist; telling the other to kill it", + friendnumber, filenumber); + send_file_control_packet(m, friendnumber, !receive_send, filenumber, FILECONTROL_KILL, 0, 0); + return -1; + } + + switch (control_type) { + case FILECONTROL_ACCEPT: { + if (receive_send && ft->status == FILESTATUS_NOT_ACCEPTED) { + ft->status = FILESTATUS_TRANSFERRING; + } else { + if (ft->paused & FILE_PAUSE_OTHER) { + ft->paused ^= FILE_PAUSE_OTHER; + } else { + LOGGER_DEBUG(m->log, "file control (friend %d, file %d): friend told us to resume file transfer that wasn't paused", + friendnumber, filenumber); + return -1; + } + } + + if (m->file_filecontrol) { + m->file_filecontrol(m, friendnumber, real_filenumber, control_type, userdata); + } + + return 0; + } + + case FILECONTROL_PAUSE: { + if ((ft->paused & FILE_PAUSE_OTHER) || ft->status != FILESTATUS_TRANSFERRING) { + LOGGER_DEBUG(m->log, "file control (friend %d, file %d): friend told us to pause file transfer that is already paused", + friendnumber, filenumber); + return -1; + } + + ft->paused |= FILE_PAUSE_OTHER; + + if (m->file_filecontrol) { + m->file_filecontrol(m, friendnumber, real_filenumber, control_type, userdata); + } + + return 0; + } + + case FILECONTROL_KILL: { + if (m->file_filecontrol) { + m->file_filecontrol(m, friendnumber, real_filenumber, control_type, userdata); + } + + ft->status = FILESTATUS_NONE; + + if (receive_send) { + --m->friendlist[friendnumber].num_sending_files; + } + + return 0; + } + + case FILECONTROL_SEEK: { + uint64_t position; + + if (length != sizeof(position)) { + LOGGER_DEBUG(m->log, "file control (friend %d, file %d): expected payload of length %d, but got %d", + friendnumber, filenumber, (uint32_t)sizeof(position), length); + return -1; + } + + /* seek can only be sent by the receiver to seek before resuming broken transfers. */ + if (ft->status != FILESTATUS_NOT_ACCEPTED || !receive_send) { + LOGGER_DEBUG(m->log, + "file control (friend %d, file %d): seek was either sent by a sender or by the receiver after accepting", + friendnumber, filenumber); + return -1; + } + + memcpy(&position, data, sizeof(position)); + net_to_host((uint8_t *) &position, sizeof(position)); + + if (position >= ft->size) { + LOGGER_DEBUG(m->log, + "file control (friend %d, file %d): seek position %lld exceeds file size %lld", + friendnumber, filenumber, (unsigned long long)position, (unsigned long long)ft->size); + return -1; + } + + ft->transferred = ft->requested = position; + return 0; + } + + default: { + LOGGER_DEBUG(m->log, "file control (friend %d, file %d): invalid file control: %d", + friendnumber, filenumber, control_type); + return -1; + } + } +} + +/**************************************/ + +/* Set the callback for msi packets. + * + * Function(Messenger *m, int friendnumber, uint8_t *data, uint16_t length, void *userdata) + */ +void m_callback_msi_packet(Messenger *m, void (*function)(Messenger *m, uint32_t, const uint8_t *, uint16_t, void *), + void *userdata) +{ + m->msi_packet = function; + m->msi_packet_userdata = userdata; +} + +/* Send an msi packet. + * + * return 1 on success + * return 0 on failure + */ +int m_msi_packet(const Messenger *m, int32_t friendnumber, const uint8_t *data, uint16_t length) +{ + return write_cryptpacket_id(m, friendnumber, PACKET_ID_MSI, data, length, 0); +} + +static int m_handle_custom_lossy_packet(void *object, int friend_num, const uint8_t *packet, uint16_t length, + void *userdata) +{ + Messenger *m = (Messenger *)object; + + if (friend_not_valid(m, friend_num)) { + return 1; + } + + if (packet[0] < (PACKET_ID_LOSSY_RANGE_START + PACKET_LOSSY_AV_RESERVED)) { + if (m->friendlist[friend_num].lossy_rtp_packethandlers[packet[0] % PACKET_LOSSY_AV_RESERVED].function) { + return m->friendlist[friend_num].lossy_rtp_packethandlers[packet[0] % PACKET_LOSSY_AV_RESERVED].function( + m, friend_num, packet, length, m->friendlist[friend_num].lossy_rtp_packethandlers[packet[0] % + PACKET_LOSSY_AV_RESERVED].object); + } + + return 1; + } + + if (m->lossy_packethandler) { + m->lossy_packethandler(m, friend_num, packet, length, userdata); + } + + return 1; +} + +void custom_lossy_packet_registerhandler(Messenger *m, void (*packet_handler_callback)(Messenger *m, + uint32_t friendnumber, const uint8_t *data, size_t len, void *object)) +{ + m->lossy_packethandler = packet_handler_callback; +} + +int m_callback_rtp_packet(Messenger *m, int32_t friendnumber, uint8_t byte, int (*packet_handler_callback)(Messenger *m, + uint32_t friendnumber, const uint8_t *data, uint16_t len, void *object), void *object) +{ + if (friend_not_valid(m, friendnumber)) { + return -1; + } + + if (byte < PACKET_ID_LOSSY_RANGE_START) { + return -1; + } + + if (byte >= (PACKET_ID_LOSSY_RANGE_START + PACKET_LOSSY_AV_RESERVED)) { + return -1; + } + + m->friendlist[friendnumber].lossy_rtp_packethandlers[byte % PACKET_LOSSY_AV_RESERVED].function = + packet_handler_callback; + m->friendlist[friendnumber].lossy_rtp_packethandlers[byte % PACKET_LOSSY_AV_RESERVED].object = object; + return 0; +} + + +int m_send_custom_lossy_packet(const Messenger *m, int32_t friendnumber, const uint8_t *data, uint32_t length) +{ + if (friend_not_valid(m, friendnumber)) { + return -1; + } + + if (length == 0 || length > MAX_CRYPTO_DATA_SIZE) { + return -2; + } + + if (data[0] < PACKET_ID_LOSSY_RANGE_START) { + return -3; + } + + if (data[0] >= (PACKET_ID_LOSSY_RANGE_START + PACKET_ID_LOSSY_RANGE_SIZE)) { + return -3; + } + + if (m->friendlist[friendnumber].status != FRIEND_ONLINE) { + return -4; + } + + if (send_lossy_cryptpacket(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c, + m->friendlist[friendnumber].friendcon_id), data, length) == -1) { + return -5; + } + + return 0; +} + +static int handle_custom_lossless_packet(void *object, int friend_num, const uint8_t *packet, uint16_t length, + void *userdata) +{ + Messenger *m = (Messenger *)object; + + if (friend_not_valid(m, friend_num)) { + return -1; + } + + if (packet[0] < PACKET_ID_LOSSLESS_RANGE_START) { + return -1; + } + + if (packet[0] >= (PACKET_ID_LOSSLESS_RANGE_START + PACKET_ID_LOSSLESS_RANGE_SIZE)) { + return -1; + } + + if (m->lossless_packethandler) { + m->lossless_packethandler(m, friend_num, packet, length, userdata); + } + + return 1; +} + +void custom_lossless_packet_registerhandler(Messenger *m, void (*packet_handler_callback)(Messenger *m, + uint32_t friendnumber, const uint8_t *data, size_t len, void *object)) +{ + m->lossless_packethandler = packet_handler_callback; +} + +int send_custom_lossless_packet(const Messenger *m, int32_t friendnumber, const uint8_t *data, uint32_t length) +{ + if (friend_not_valid(m, friendnumber)) { + return -1; + } + + if (length == 0 || length > MAX_CRYPTO_DATA_SIZE) { + return -2; + } + + if (data[0] < PACKET_ID_LOSSLESS_RANGE_START) { + return -3; + } + + if (data[0] >= (PACKET_ID_LOSSLESS_RANGE_START + PACKET_ID_LOSSLESS_RANGE_SIZE)) { + return -3; + } + + if (m->friendlist[friendnumber].status != FRIEND_ONLINE) { + return -4; + } + + if (write_cryptpacket(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c, + m->friendlist[friendnumber].friendcon_id), data, length, 1) == -1) { + return -5; + } + + return 0; +} + +/* Function to filter out some friend requests*/ +static int friend_already_added(const uint8_t *real_pk, void *data) +{ + const Messenger *m = (const Messenger *)data; + + if (getfriend_id(m, real_pk) == -1) { + return 0; + } + + return -1; +} + +/* Run this at startup. */ +Messenger *new_messenger(Messenger_Options *options, unsigned int *error) +{ + if (!options) { + return NULL; + } + + Messenger *m = (Messenger *)calloc(1, sizeof(Messenger)); + + if (error) { + *error = MESSENGER_ERROR_OTHER; + } + + if (!m) { + return NULL; + } + + Logger *log = NULL; + + if (options->log_callback) { + log = logger_new(); + + if (log != NULL) { + logger_callback_log(log, options->log_callback, m, options->log_user_data); + } + } + + m->log = log; + + unsigned int net_err = 0; + + if (options->udp_disabled) { + /* this is the easiest way to completely disable UDP without changing too much code. */ + m->net = (Networking_Core *)calloc(1, sizeof(Networking_Core)); + } else { + IP ip; + ip_init(&ip, options->ipv6enabled); + m->net = new_networking_ex(log, ip, options->port_range[0], options->port_range[1], &net_err); + } + + if (m->net == NULL) { + free(m); + + if (error && net_err == 1) { + *error = MESSENGER_ERROR_PORT; + } + + return NULL; + } + + m->dht = new_DHT(m->log, m->net, options->hole_punching_enabled); + + if (m->dht == NULL) { + kill_networking(m->net); + free(m); + return NULL; + } + + m->net_crypto = new_net_crypto(m->log, m->dht, &options->proxy_info); + + if (m->net_crypto == NULL) { + kill_networking(m->net); + kill_DHT(m->dht); + free(m); + return NULL; + } + + m->onion = new_onion(m->dht); + m->onion_a = new_onion_announce(m->dht); + m->onion_c = new_onion_client(m->net_crypto); + m->fr_c = new_friend_connections(m->onion_c, options->local_discovery_enabled); + + if (!(m->onion && m->onion_a && m->onion_c)) { + kill_friend_connections(m->fr_c); + kill_onion(m->onion); + kill_onion_announce(m->onion_a); + kill_onion_client(m->onion_c); + kill_net_crypto(m->net_crypto); + kill_DHT(m->dht); + kill_networking(m->net); + free(m); + return NULL; + } + + if (options->tcp_server_port) { + m->tcp_server = new_TCP_server(options->ipv6enabled, 1, &options->tcp_server_port, m->dht->self_secret_key, m->onion); + + if (m->tcp_server == NULL) { + kill_friend_connections(m->fr_c); + kill_onion(m->onion); + kill_onion_announce(m->onion_a); + kill_onion_client(m->onion_c); + kill_net_crypto(m->net_crypto); + kill_DHT(m->dht); + kill_networking(m->net); + free(m); + + if (error) { + *error = MESSENGER_ERROR_TCP_SERVER; + } + + return NULL; + } + } + + m->options = *options; + friendreq_init(&(m->fr), m->fr_c); + set_nospam(&(m->fr), random_int()); + set_filter_function(&(m->fr), &friend_already_added, m); + + m->lastdump = 0; + + if (error) { + *error = MESSENGER_ERROR_NONE; + } + + return m; +} + +/* Run this before closing shop. */ +void kill_messenger(Messenger *m) +{ + if (!m) { + return; + } + + uint32_t i; + + if (m->tcp_server) { + kill_TCP_server(m->tcp_server); + } + + kill_friend_connections(m->fr_c); + kill_onion(m->onion); + kill_onion_announce(m->onion_a); + kill_onion_client(m->onion_c); + kill_net_crypto(m->net_crypto); + kill_DHT(m->dht); + kill_networking(m->net); + + for (i = 0; i < m->numfriends; ++i) { + clear_receipts(m, i); + } + + logger_kill(m->log); + free(m->friendlist); + 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, void *userdata) +{ + Friend *f = &m->friendlist[i]; + + if (f->friendrequest_lastsent + f->friendrequest_timeout < t) { + set_friend_status(m, i, FRIEND_ADDED, userdata); + /* Double the default timeout every time if friendrequest is assumed + * to have been sent unsuccessfully. + */ + f->friendrequest_timeout *= 2; + } +} + +static int m_handle_status(void *object, int i, uint8_t status, void *userdata) +{ + Messenger *m = (Messenger *)object; + + if (status) { /* Went online. */ + send_online_packet(m, i); + } else { /* Went offline. */ + if (m->friendlist[i].status == FRIEND_ONLINE) { + set_friend_status(m, i, FRIEND_CONFIRMED, userdata); + } + } + + return 0; +} + +static int m_handle_packet(void *object, int i, const uint8_t *temp, uint16_t len, void *userdata) +{ + if (len == 0) { + return -1; + } + + Messenger *m = (Messenger *)object; + uint8_t packet_id = temp[0]; + const uint8_t *data = temp + 1; + uint32_t data_length = len - 1; + + if (m->friendlist[i].status != FRIEND_ONLINE) { + if (packet_id == PACKET_ID_ONLINE && len == 1) { + set_friend_status(m, i, FRIEND_ONLINE, userdata); + send_online_packet(m, i); + } else { + return -1; + } + } + + switch (packet_id) { + case PACKET_ID_OFFLINE: { + if (data_length != 0) { + break; + } + + set_friend_status(m, i, FRIEND_CONFIRMED, userdata); + break; + } + + case PACKET_ID_NICKNAME: { + if (data_length > MAX_NAME_LENGTH) { + break; + } + + /* Make sure the NULL terminator is present. */ + VLA(uint8_t, data_terminated, data_length + 1); + memcpy(data_terminated, data, data_length); + data_terminated[data_length] = 0; + + /* inform of namechange before we overwrite the old name */ + if (m->friend_namechange) { + m->friend_namechange(m, i, data_terminated, data_length, userdata); + } + + memcpy(m->friendlist[i].name, data_terminated, data_length); + m->friendlist[i].name_length = data_length; + + break; + } + + case PACKET_ID_STATUSMESSAGE: { + if (data_length > MAX_STATUSMESSAGE_LENGTH) { + break; + } + + /* Make sure the NULL terminator is present. */ + VLA(uint8_t, data_terminated, data_length + 1); + memcpy(data_terminated, data, data_length); + data_terminated[data_length] = 0; + + if (m->friend_statusmessagechange) { + m->friend_statusmessagechange(m, i, data_terminated, data_length, userdata); + } + + set_friend_statusmessage(m, i, data_terminated, data_length); + break; + } + + case PACKET_ID_USERSTATUS: { + if (data_length != 1) { + break; + } + + USERSTATUS status = (USERSTATUS)data[0]; + + if (status >= USERSTATUS_INVALID) { + break; + } + + if (m->friend_userstatuschange) { + m->friend_userstatuschange(m, i, status, userdata); + } + + set_friend_userstatus(m, i, status); + break; + } + + case PACKET_ID_TYPING: { + if (data_length != 1) { + break; + } + + bool typing = !!data[0]; + + set_friend_typing(m, i, typing); + + if (m->friend_typingchange) { + m->friend_typingchange(m, i, typing, userdata); + } + + break; + } + + case PACKET_ID_MESSAGE: // fall-through + case PACKET_ID_ACTION: { + if (data_length == 0) { + break; + } + + const uint8_t *message = data; + uint16_t message_length = data_length; + + /* Make sure the NULL terminator is present. */ + VLA(uint8_t, message_terminated, message_length + 1); + memcpy(message_terminated, message, message_length); + message_terminated[message_length] = 0; + uint8_t type = packet_id - PACKET_ID_MESSAGE; + + if (m->friend_message) { + (*m->friend_message)(m, i, type, message_terminated, message_length, userdata); + } + + break; + } + + case PACKET_ID_INVITE_CONFERENCE: { + if (data_length == 0) { + break; + } + + if (m->conference_invite) { + (*m->conference_invite)(m, i, data, data_length, userdata); + } + + break; + } + + case PACKET_ID_FILE_SENDREQUEST: { + const unsigned int head_length = 1 + sizeof(uint32_t) + sizeof(uint64_t) + FILE_ID_LENGTH; + + if (data_length < head_length) { + break; + } + + uint8_t filenumber = data[0]; + + if (filenumber >= MAX_CONCURRENT_FILE_PIPES) { + break; + } + + uint64_t filesize; + uint32_t file_type; + uint16_t filename_length = data_length - head_length; + + if (filename_length > MAX_FILENAME_LENGTH) { + break; + } + + memcpy(&file_type, data + 1, sizeof(file_type)); + file_type = net_ntohl(file_type); + + memcpy(&filesize, data + 1 + sizeof(uint32_t), sizeof(filesize)); + net_to_host((uint8_t *) &filesize, sizeof(filesize)); + struct File_Transfers *ft = &m->friendlist[i].file_receiving[filenumber]; + + if (ft->status != FILESTATUS_NONE) { + break; + } + + ft->status = FILESTATUS_NOT_ACCEPTED; + ft->size = filesize; + ft->transferred = 0; + ft->paused = FILE_PAUSE_NOT; + memcpy(ft->id, data + 1 + sizeof(uint32_t) + sizeof(uint64_t), FILE_ID_LENGTH); + + VLA(uint8_t, filename_terminated, filename_length + 1); + uint8_t *filename = NULL; + + if (filename_length) { + /* Force NULL terminate file name. */ + memcpy(filename_terminated, data + head_length, filename_length); + filename_terminated[filename_length] = 0; + filename = filename_terminated; + } + + uint32_t real_filenumber = filenumber; + real_filenumber += 1; + real_filenumber <<= 16; + + if (m->file_sendrequest) { + (*m->file_sendrequest)(m, i, real_filenumber, file_type, filesize, filename, filename_length, + userdata); + } + + break; + } + + case PACKET_ID_FILE_CONTROL: { + if (data_length < 3) { + break; + } + + uint8_t send_receive = data[0]; + uint8_t filenumber = data[1]; + uint8_t control_type = data[2]; + + if (filenumber >= MAX_CONCURRENT_FILE_PIPES) { + break; + } + + if (handle_filecontrol(m, i, send_receive, filenumber, control_type, data + 3, data_length - 3, userdata) == -1) { + // TODO(iphydf): Do something different here? Right now, this + // check is pointless. + break; + } + + break; + } + + case PACKET_ID_FILE_DATA: { + if (data_length < 1) { + break; + } + + uint8_t filenumber = data[0]; + + if (filenumber >= MAX_CONCURRENT_FILE_PIPES) { + break; + } + + struct File_Transfers *ft = &m->friendlist[i].file_receiving[filenumber]; + + if (ft->status != FILESTATUS_TRANSFERRING) { + break; + } + + uint64_t position = ft->transferred; + uint32_t real_filenumber = filenumber; + real_filenumber += 1; + real_filenumber <<= 16; + uint16_t file_data_length = (data_length - 1); + const uint8_t *file_data; + + if (file_data_length == 0) { + file_data = NULL; + } else { + file_data = data + 1; + } + + /* Prevent more data than the filesize from being passed to clients. */ + if ((ft->transferred + file_data_length) > ft->size) { + file_data_length = ft->size - ft->transferred; + } + + if (m->file_filedata) { + (*m->file_filedata)(m, i, real_filenumber, position, file_data, file_data_length, userdata); + } + + ft->transferred += file_data_length; + + if (file_data_length && (ft->transferred >= ft->size || file_data_length != MAX_FILE_DATA_SIZE)) { + file_data_length = 0; + file_data = NULL; + position = ft->transferred; + + /* Full file received. */ + if (m->file_filedata) { + (*m->file_filedata)(m, i, real_filenumber, position, file_data, file_data_length, userdata); + } + } + + /* Data is zero, filetransfer is over. */ + if (file_data_length == 0) { + ft->status = FILESTATUS_NONE; + } + + break; + } + + case PACKET_ID_MSI: { + if (data_length == 0) { + break; + } + + if (m->msi_packet) { + (*m->msi_packet)(m, i, data, data_length, m->msi_packet_userdata); + } + + break; + } + + default: { + handle_custom_lossless_packet(object, i, temp, len, userdata); + break; + } + } + + return 0; +} + +static void do_friends(Messenger *m, void *userdata) +{ + uint32_t i; + uint64_t temp_time = unix_time(); + + for (i = 0; i < m->numfriends; ++i) { + if (m->friendlist[i].status == FRIEND_ADDED) { + int fr = send_friend_request_packet(m->fr_c, m->friendlist[i].friendcon_id, m->friendlist[i].friendrequest_nospam, + m->friendlist[i].info, + m->friendlist[i].info_size); + + if (fr >= 0) { + set_friend_status(m, i, FRIEND_REQUESTED, userdata); + m->friendlist[i].friendrequest_lastsent = temp_time; + } + } + + if (m->friendlist[i].status == FRIEND_REQUESTED + || m->friendlist[i].status == FRIEND_CONFIRMED) { /* friend is not online. */ + if (m->friendlist[i].status == FRIEND_REQUESTED) { + /* 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. + */ + check_friend_request_timed_out(m, i, temp_time, userdata); + } + } + + if (m->friendlist[i].status == FRIEND_ONLINE) { /* friend is online. */ + if (m->friendlist[i].name_sent == 0) { + if (m_sendname(m, i, m->name, m->name_length)) { + m->friendlist[i].name_sent = 1; + } + } + + if (m->friendlist[i].statusmessage_sent == 0) { + if (send_statusmessage(m, i, m->statusmessage, m->statusmessage_length)) { + m->friendlist[i].statusmessage_sent = 1; + } + } + + if (m->friendlist[i].userstatus_sent == 0) { + if (send_userstatus(m, i, m->userstatus)) { + m->friendlist[i].userstatus_sent = 1; + } + } + + if (m->friendlist[i].user_istyping_sent == 0) { + if (send_user_istyping(m, i, m->friendlist[i].user_istyping)) { + m->friendlist[i].user_istyping_sent = 1; + } + } + + check_friend_tcp_udp(m, i, userdata); + do_receipts(m, i, userdata); + do_reqchunk_filecb(m, i, userdata); + + m->friendlist[i].last_seen_time = (uint64_t) time(NULL); + } + } +} + +static void connection_status_cb(Messenger *m, void *userdata) +{ + unsigned int conn_status = onion_connection_status(m->onion_c); + + if (conn_status != m->last_connection_status) { + if (m->core_connection_change) { + (*m->core_connection_change)(m, conn_status, userdata); + } + + m->last_connection_status = conn_status; + } +} + + +#define DUMPING_CLIENTS_FRIENDS_EVERY_N_SECONDS 60UL + +#define IDSTRING_LEN (CRYPTO_PUBLIC_KEY_SIZE * 2 + 1) +/* id_str should be of length at least IDSTRING_LEN */ +static char *id_to_string(const uint8_t *pk, char *id_str, size_t length) +{ + if (length < IDSTRING_LEN) { + snprintf(id_str, length, "Bad buf length"); + return id_str; + } + + for (uint32_t i = 0; i < CRYPTO_PUBLIC_KEY_SIZE; i++) { + sprintf(&id_str[i * 2], "%02X", pk[i]); + } + + id_str[CRYPTO_PUBLIC_KEY_SIZE * 2] = 0; + return id_str; +} + +/* Minimum messenger run interval in ms + TODO(mannol): A/V */ +#define MIN_RUN_INTERVAL 50 + +/* Return the time in milliseconds before do_messenger() should be called again + * for optimal performance. + * + * returns time (in ms) before the next do_messenger() needs to be run on success. + */ +uint32_t messenger_run_interval(const Messenger *m) +{ + uint32_t crypto_interval = crypto_run_interval(m->net_crypto); + + if (crypto_interval > MIN_RUN_INTERVAL) { + return MIN_RUN_INTERVAL; + } + + return crypto_interval; +} + +/* The main loop that needs to be run at least 20 times per second. */ +void do_messenger(Messenger *m, void *userdata) +{ + // Add the TCP relays, but only if this is the first time calling do_messenger + if (m->has_added_relays == 0) { + m->has_added_relays = 1; + + int i; + + for (i = 0; i < NUM_SAVED_TCP_RELAYS; ++i) { + add_tcp_relay(m->net_crypto, m->loaded_relays[i].ip_port, m->loaded_relays[i].public_key); + } + + if (m->tcp_server) { + /* Add self tcp server. */ + IP_Port local_ip_port; + local_ip_port.port = m->options.tcp_server_port; + local_ip_port.ip.family = TOX_AF_INET; + local_ip_port.ip.ip4 = get_ip4_loopback(); + add_tcp_relay(m->net_crypto, local_ip_port, + tcp_server_public_key(m->tcp_server)); + } + } + + unix_time_update(); + + if (!m->options.udp_disabled) { + networking_poll(m->net, userdata); + do_DHT(m->dht); + } + + if (m->tcp_server) { + do_TCP_server(m->tcp_server); + } + + do_net_crypto(m->net_crypto, userdata); + do_onion_client(m->onion_c); + do_friend_connections(m->fr_c, userdata); + do_friends(m, userdata); + connection_status_cb(m, userdata); + + if (unix_time() > m->lastdump + DUMPING_CLIENTS_FRIENDS_EVERY_N_SECONDS) { + m->lastdump = unix_time(); + uint32_t client, last_pinged; + + for (client = 0; client < LCLIENT_LIST; client++) { + Client_data *cptr = &m->dht->close_clientlist[client]; + IPPTsPng *assoc = NULL; + uint32_t a; + + for (a = 0, assoc = &cptr->assoc4; a < 2; a++, assoc = &cptr->assoc6) { + if (ip_isset(&assoc->ip_port.ip)) { + last_pinged = m->lastdump - assoc->last_pinged; + + if (last_pinged > 999) { + last_pinged = 999; + } + + char ip_str[IP_NTOA_LEN]; + char id_str[IDSTRING_LEN]; + LOGGER_TRACE(m->log, "C[%2u] %s:%u [%3u] %s", + client, ip_ntoa(&assoc->ip_port.ip, ip_str, sizeof(ip_str)), + net_ntohs(assoc->ip_port.port), last_pinged, + id_to_string(cptr->public_key, id_str, sizeof(id_str))); + } + } + } + + + uint32_t friend_idx, dhtfriend; + + /* dht contains additional "friends" (requests) */ + uint32_t num_dhtfriends = m->dht->num_friends; + VLA(int32_t, m2dht, num_dhtfriends); + VLA(int32_t, dht2m, num_dhtfriends); + + for (friend_idx = 0; friend_idx < num_dhtfriends; friend_idx++) { + m2dht[friend_idx] = -1; + dht2m[friend_idx] = -1; + + if (friend_idx >= m->numfriends) { + continue; + } + + for (dhtfriend = 0; dhtfriend < m->dht->num_friends; dhtfriend++) { + if (id_equal(m->friendlist[friend_idx].real_pk, m->dht->friends_list[dhtfriend].public_key)) { + m2dht[friend_idx] = dhtfriend; + break; + } + } + } + + for (friend_idx = 0; friend_idx < num_dhtfriends; friend_idx++) { + if (m2dht[friend_idx] >= 0) { + dht2m[m2dht[friend_idx]] = friend_idx; + } + } + + if (m->numfriends != m->dht->num_friends) { + LOGGER_TRACE(m->log, "Friend num in DHT %u != friend num in msger %u\n", m->dht->num_friends, m->numfriends); + } + + Friend *msgfptr; + DHT_Friend *dhtfptr; + + for (friend_idx = 0; friend_idx < num_dhtfriends; friend_idx++) { + if (dht2m[friend_idx] >= 0) { + msgfptr = &m->friendlist[dht2m[friend_idx]]; + } else { + msgfptr = NULL; + } + + dhtfptr = &m->dht->friends_list[friend_idx]; + + if (msgfptr) { + char id_str[IDSTRING_LEN]; + LOGGER_TRACE(m->log, "F[%2u:%2u] <%s> %s", + dht2m[friend_idx], friend_idx, msgfptr->name, + id_to_string(msgfptr->real_pk, id_str, sizeof(id_str))); + } else { + char id_str[IDSTRING_LEN]; + LOGGER_TRACE(m->log, "F[--:%2u] %s", friend_idx, + id_to_string(dhtfptr->public_key, id_str, sizeof(id_str))); + } + + for (client = 0; client < MAX_FRIEND_CLIENTS; client++) { + Client_data *cptr = &dhtfptr->client_list[client]; + IPPTsPng *assoc = NULL; + uint32_t a; + + for (a = 0, assoc = &cptr->assoc4; a < 2; a++, assoc = &cptr->assoc6) { + if (ip_isset(&assoc->ip_port.ip)) { + last_pinged = m->lastdump - assoc->last_pinged; + + if (last_pinged > 999) { + last_pinged = 999; + } + + char ip_str[IP_NTOA_LEN]; + char id_str[IDSTRING_LEN]; + LOGGER_TRACE(m->log, "F[%2u] => C[%2u] %s:%u [%3u] %s", + friend_idx, client, ip_ntoa(&assoc->ip_port.ip, ip_str, sizeof(ip_str)), + net_ntohs(assoc->ip_port.port), last_pinged, + id_to_string(cptr->public_key, id_str, sizeof(id_str))); + } + } + } + } + } +} + +/* new messenger format for load/save, more robust and forward compatible */ + +#define MESSENGER_STATE_COOKIE_GLOBAL 0x15ed1b1f + +#define MESSENGER_STATE_COOKIE_TYPE 0x01ce +#define MESSENGER_STATE_TYPE_NOSPAMKEYS 1 +#define MESSENGER_STATE_TYPE_DHT 2 +#define MESSENGER_STATE_TYPE_FRIENDS 3 +#define MESSENGER_STATE_TYPE_NAME 4 +#define MESSENGER_STATE_TYPE_STATUSMESSAGE 5 +#define MESSENGER_STATE_TYPE_STATUS 6 +#define MESSENGER_STATE_TYPE_TCP_RELAY 10 +#define MESSENGER_STATE_TYPE_PATH_NODE 11 +#define MESSENGER_STATE_TYPE_END 255 + +#define SAVED_FRIEND_REQUEST_SIZE 1024 +#define NUM_SAVED_PATH_NODES 8 + +struct SAVED_FRIEND { + uint8_t status; + uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t info[SAVED_FRIEND_REQUEST_SIZE]; // the data that is sent during the friend requests we do. + uint16_t info_size; // Length of the info. + uint8_t name[MAX_NAME_LENGTH]; + uint16_t name_length; + uint8_t statusmessage[MAX_STATUSMESSAGE_LENGTH]; + uint16_t statusmessage_length; + uint8_t userstatus; + uint32_t friendrequest_nospam; + uint64_t last_seen_time; +}; + +static uint32_t friend_size() +{ + uint32_t data = 0; + const struct SAVED_FRIEND temp = { 0 }; + +#define VALUE_MEMBER(NAME) data += sizeof(temp.NAME) +#define ARRAY_MEMBER(NAME) data += sizeof(temp.NAME) + + // Exactly the same in friend_load, friend_save, and friend_size + VALUE_MEMBER(status); + ARRAY_MEMBER(real_pk); + ARRAY_MEMBER(info); + data++; // padding + VALUE_MEMBER(info_size); + ARRAY_MEMBER(name); + VALUE_MEMBER(name_length); + ARRAY_MEMBER(statusmessage); + data++; // padding + VALUE_MEMBER(statusmessage_length); + VALUE_MEMBER(userstatus); + data += 3; // padding + VALUE_MEMBER(friendrequest_nospam); + VALUE_MEMBER(last_seen_time); + +#undef VALUE_MEMBER +#undef ARRAY_MEMBER + + return data; +} + +static uint32_t saved_friendslist_size(const Messenger *m) +{ + return count_friendlist(m) * friend_size(); +} + +static uint8_t *friend_save(const struct SAVED_FRIEND *temp, uint8_t *data) +{ +#define VALUE_MEMBER(NAME) \ + memcpy(data, &temp->NAME, sizeof(temp->NAME)); \ + data += sizeof(temp->NAME) + +#define ARRAY_MEMBER(NAME) \ + memcpy(data, temp->NAME, sizeof(temp->NAME)); \ + data += sizeof(temp->NAME) + + // Exactly the same in friend_load, friend_save, and friend_size + VALUE_MEMBER(status); + ARRAY_MEMBER(real_pk); + ARRAY_MEMBER(info); + data++; // padding + VALUE_MEMBER(info_size); + ARRAY_MEMBER(name); + VALUE_MEMBER(name_length); + ARRAY_MEMBER(statusmessage); + data++; // padding + VALUE_MEMBER(statusmessage_length); + VALUE_MEMBER(userstatus); + data += 3; // padding + VALUE_MEMBER(friendrequest_nospam); + VALUE_MEMBER(last_seen_time); + +#undef VALUE_MEMBER +#undef ARRAY_MEMBER + + return data; +} + +static uint32_t friends_list_save(const Messenger *m, uint8_t *data) +{ + uint32_t i; + uint32_t num = 0; + uint8_t *cur_data = data; + + for (i = 0; i < m->numfriends; i++) { + if (m->friendlist[i].status > 0) { + struct SAVED_FRIEND temp = { 0 }; + temp.status = m->friendlist[i].status; + memcpy(temp.real_pk, m->friendlist[i].real_pk, CRYPTO_PUBLIC_KEY_SIZE); + + if (temp.status < 3) { + const size_t friendrequest_length = + MIN(m->friendlist[i].info_size, + MIN(SAVED_FRIEND_REQUEST_SIZE, MAX_FRIEND_REQUEST_DATA_SIZE)); + memcpy(temp.info, m->friendlist[i].info, friendrequest_length); + + temp.info_size = net_htons(m->friendlist[i].info_size); + temp.friendrequest_nospam = m->friendlist[i].friendrequest_nospam; + } else { + memcpy(temp.name, m->friendlist[i].name, m->friendlist[i].name_length); + temp.name_length = net_htons(m->friendlist[i].name_length); + memcpy(temp.statusmessage, m->friendlist[i].statusmessage, m->friendlist[i].statusmessage_length); + temp.statusmessage_length = net_htons(m->friendlist[i].statusmessage_length); + temp.userstatus = m->friendlist[i].userstatus; + + uint8_t last_seen_time[sizeof(uint64_t)]; + memcpy(last_seen_time, &m->friendlist[i].last_seen_time, sizeof(uint64_t)); + host_to_net(last_seen_time, sizeof(uint64_t)); + memcpy(&temp.last_seen_time, last_seen_time, sizeof(uint64_t)); + } + + uint8_t *next_data = friend_save(&temp, cur_data); + assert(next_data - cur_data == friend_size()); +#ifdef __LP64__ + assert(memcmp(cur_data, &temp, friend_size()) == 0); +#endif + cur_data = next_data; + num++; + } + } + + assert(cur_data - data == num * friend_size()); + return cur_data - data; +} + +static const uint8_t *friend_load(struct SAVED_FRIEND *temp, const uint8_t *data) +{ +#define VALUE_MEMBER(NAME) \ + memcpy(&temp->NAME, data, sizeof(temp->NAME)); \ + data += sizeof(temp->NAME) + +#define ARRAY_MEMBER(NAME) \ + memcpy(temp->NAME, data, sizeof(temp->NAME)); \ + data += sizeof(temp->NAME) + + // Exactly the same in friend_load, friend_save, and friend_size + VALUE_MEMBER(status); + ARRAY_MEMBER(real_pk); + ARRAY_MEMBER(info); + data++; // padding + VALUE_MEMBER(info_size); + ARRAY_MEMBER(name); + VALUE_MEMBER(name_length); + ARRAY_MEMBER(statusmessage); + data++; // padding + VALUE_MEMBER(statusmessage_length); + VALUE_MEMBER(userstatus); + data += 3; // padding + VALUE_MEMBER(friendrequest_nospam); + VALUE_MEMBER(last_seen_time); + +#undef VALUE_MEMBER +#undef ARRAY_MEMBER + + return data; +} + +static int friends_list_load(Messenger *m, const uint8_t *data, uint32_t length) +{ + if (length % friend_size() != 0) { + return -1; + } + + uint32_t num = length / friend_size(); + uint32_t i; + const uint8_t *cur_data = data; + + for (i = 0; i < num; ++i) { + struct SAVED_FRIEND temp = { 0 }; + const uint8_t *next_data = friend_load(&temp, cur_data); + assert(next_data - cur_data == friend_size()); +#ifdef __LP64__ + assert(memcmp(&temp, cur_data, friend_size()) == 0); +#endif + cur_data = next_data; + + if (temp.status >= 3) { + int fnum = m_addfriend_norequest(m, temp.real_pk); + + if (fnum < 0) { + continue; + } + + setfriendname(m, fnum, temp.name, net_ntohs(temp.name_length)); + set_friend_statusmessage(m, fnum, temp.statusmessage, net_ntohs(temp.statusmessage_length)); + set_friend_userstatus(m, fnum, temp.userstatus); + uint8_t last_seen_time[sizeof(uint64_t)]; + memcpy(last_seen_time, &temp.last_seen_time, sizeof(uint64_t)); + net_to_host(last_seen_time, sizeof(uint64_t)); + memcpy(&m->friendlist[fnum].last_seen_time, last_seen_time, sizeof(uint64_t)); + } else if (temp.status != 0) { + /* TODO(irungentoo): This is not a good way to do this. */ + uint8_t address[FRIEND_ADDRESS_SIZE]; + id_copy(address, temp.real_pk); + memcpy(address + CRYPTO_PUBLIC_KEY_SIZE, &(temp.friendrequest_nospam), sizeof(uint32_t)); + uint16_t checksum = address_checksum(address, FRIEND_ADDRESS_SIZE - sizeof(checksum)); + memcpy(address + CRYPTO_PUBLIC_KEY_SIZE + sizeof(uint32_t), &checksum, sizeof(checksum)); + m_addfriend(m, address, temp.info, net_ntohs(temp.info_size)); + } + } + + return num; +} + +/* return size of the messenger data (for saving) */ +uint32_t messenger_size(const Messenger *m) +{ + uint32_t size32 = sizeof(uint32_t), sizesubhead = size32 * 2; + return size32 * 2 // global cookie + + sizesubhead + sizeof(uint32_t) + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SECRET_KEY_SIZE + + sizesubhead + DHT_size(m->dht) // DHT + + sizesubhead + saved_friendslist_size(m) // Friendlist itself. + + sizesubhead + m->name_length // Own nickname. + + sizesubhead + m->statusmessage_length // status message + + sizesubhead + 1 // status + + sizesubhead + NUM_SAVED_TCP_RELAYS * packed_node_size(TCP_INET6) //TCP relays + + sizesubhead + NUM_SAVED_PATH_NODES * packed_node_size(TCP_INET6) //saved path nodes + + sizesubhead; +} + +static uint8_t *messenger_save_subheader(uint8_t *data, uint32_t len, uint16_t type) +{ + host_to_lendian32(data, len); + data += sizeof(uint32_t); + host_to_lendian32(data, (host_tolendian16(MESSENGER_STATE_COOKIE_TYPE) << 16) | host_tolendian16(type)); + data += sizeof(uint32_t); + return data; +} + +/* Save the messenger in data of size Messenger_size(). */ +void messenger_save(const Messenger *m, uint8_t *data) +{ + memset(data, 0, messenger_size(m)); + + uint32_t len; + uint16_t type; + uint32_t size32 = sizeof(uint32_t); + + memset(data, 0, size32); + data += size32; + host_to_lendian32(data, MESSENGER_STATE_COOKIE_GLOBAL); + data += size32; + + assert(sizeof(get_nospam(&m->fr)) == sizeof(uint32_t)); + len = size32 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SECRET_KEY_SIZE; + type = MESSENGER_STATE_TYPE_NOSPAMKEYS; + data = messenger_save_subheader(data, len, type); + *(uint32_t *)data = get_nospam(&(m->fr)); + save_keys(m->net_crypto, data + size32); + data += len; + + len = saved_friendslist_size(m); + type = MESSENGER_STATE_TYPE_FRIENDS; + data = messenger_save_subheader(data, len, type); + friends_list_save(m, data); + data += len; + + len = m->name_length; + type = MESSENGER_STATE_TYPE_NAME; + data = messenger_save_subheader(data, len, type); + memcpy(data, m->name, len); + data += len; + + len = m->statusmessage_length; + type = MESSENGER_STATE_TYPE_STATUSMESSAGE; + data = messenger_save_subheader(data, len, type); + memcpy(data, m->statusmessage, len); + data += len; + + len = 1; + type = MESSENGER_STATE_TYPE_STATUS; + data = messenger_save_subheader(data, len, type); + *data = m->userstatus; + data += len; + + len = DHT_size(m->dht); + type = MESSENGER_STATE_TYPE_DHT; + data = messenger_save_subheader(data, len, type); + DHT_save(m->dht, data); + data += len; + + Node_format relays[NUM_SAVED_TCP_RELAYS]; + type = MESSENGER_STATE_TYPE_TCP_RELAY; + uint8_t *temp_data = data; + data = messenger_save_subheader(temp_data, 0, type); + unsigned int num = copy_connected_tcp_relays(m->net_crypto, relays, NUM_SAVED_TCP_RELAYS); + int l = pack_nodes(data, NUM_SAVED_TCP_RELAYS * packed_node_size(TCP_INET6), relays, num); + + if (l > 0) { + len = l; + data = messenger_save_subheader(temp_data, len, type); + data += len; + } + + Node_format nodes[NUM_SAVED_PATH_NODES]; + type = MESSENGER_STATE_TYPE_PATH_NODE; + temp_data = data; + data = messenger_save_subheader(data, 0, type); + memset(nodes, 0, sizeof(nodes)); + num = onion_backup_nodes(m->onion_c, nodes, NUM_SAVED_PATH_NODES); + l = pack_nodes(data, NUM_SAVED_PATH_NODES * packed_node_size(TCP_INET6), nodes, num); + + if (l > 0) { + len = l; + data = messenger_save_subheader(temp_data, len, type); + data += len; + } + + messenger_save_subheader(data, 0, MESSENGER_STATE_TYPE_END); +} + +static int messenger_load_state_callback(void *outer, const uint8_t *data, uint32_t length, uint16_t type) +{ + Messenger *m = (Messenger *)outer; + + switch (type) { + case MESSENGER_STATE_TYPE_NOSPAMKEYS: + if (length == CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SECRET_KEY_SIZE + sizeof(uint32_t)) { + set_nospam(&(m->fr), *(const uint32_t *)data); + load_secret_key(m->net_crypto, (&data[sizeof(uint32_t)]) + CRYPTO_PUBLIC_KEY_SIZE); + + if (public_key_cmp((&data[sizeof(uint32_t)]), m->net_crypto->self_public_key) != 0) { + return -1; + } + } else { + return -1; /* critical */ + } + + break; + + case MESSENGER_STATE_TYPE_DHT: + DHT_load(m->dht, data, length); + break; + + case MESSENGER_STATE_TYPE_FRIENDS: + friends_list_load(m, data, length); + break; + + case MESSENGER_STATE_TYPE_NAME: + if ((length > 0) && (length <= MAX_NAME_LENGTH)) { + setname(m, data, length); + } + + break; + + case MESSENGER_STATE_TYPE_STATUSMESSAGE: + if ((length > 0) && (length <= MAX_STATUSMESSAGE_LENGTH)) { + m_set_statusmessage(m, data, length); + } + + break; + + case MESSENGER_STATE_TYPE_STATUS: + if (length == 1) { + m_set_userstatus(m, *data); + } + + break; + + case MESSENGER_STATE_TYPE_TCP_RELAY: { + if (length == 0) { + break; + } + + unpack_nodes(m->loaded_relays, NUM_SAVED_TCP_RELAYS, 0, data, length, 1); + m->has_added_relays = 0; + + break; + } + + case MESSENGER_STATE_TYPE_PATH_NODE: { + Node_format nodes[NUM_SAVED_PATH_NODES]; + + if (length == 0) { + break; + } + + int i, num = unpack_nodes(nodes, NUM_SAVED_PATH_NODES, 0, data, length, 0); + + for (i = 0; i < num; ++i) { + onion_add_bs_path_node(m->onion_c, nodes[i].ip_port, nodes[i].public_key); + } + + break; + } + + case MESSENGER_STATE_TYPE_END: { + if (length != 0) { + return -1; + } + + return -2; + } + + default: + LOGGER_ERROR(m->log, "Load state: contains unrecognized part (len %u, type %u)\n", + length, type); + break; + } + + return 0; +} + +/* Load the messenger from data of size length. */ +int messenger_load(Messenger *m, const uint8_t *data, uint32_t length) +{ + uint32_t data32[2]; + uint32_t cookie_len = sizeof(data32); + + if (length < cookie_len) { + return -1; + } + + memcpy(data32, data, sizeof(uint32_t)); + lendian_to_host32(data32 + 1, data + sizeof(uint32_t)); + + if (!data32[0] && (data32[1] == MESSENGER_STATE_COOKIE_GLOBAL)) { + return load_state(messenger_load_state_callback, m->log, m, data + cookie_len, + length - cookie_len, MESSENGER_STATE_COOKIE_TYPE); + } + + return -1; +} + +/* Return the number of friends in the instance m. + * You should use this to determine how much memory to allocate + * for copy_friendlist. */ +uint32_t count_friendlist(const Messenger *m) +{ + uint32_t ret = 0; + uint32_t i; + + for (i = 0; i < m->numfriends; i++) { + if (m->friendlist[i].status > 0) { + ret++; + } + } + + return ret; +} + +/* Copy a list of valid friend 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_friendlist(Messenger const *m, uint32_t *out_list, uint32_t list_size) +{ + if (!out_list) { + return 0; + } + + if (m->numfriends == 0) { + return 0; + } + + uint32_t i; + uint32_t ret = 0; + + for (i = 0; i < m->numfriends; i++) { + if (ret >= list_size) { + break; /* Abandon ship */ + } + + if (m->friendlist[i].status > 0) { + out_list[ret] = i; + ret++; + } + } + + return ret; +} diff --git a/libs/libtox/src/toxcore/Messenger.h b/libs/libtox/src/toxcore/Messenger.h new file mode 100644 index 0000000000..e1dba69886 --- /dev/null +++ b/libs/libtox/src/toxcore/Messenger.h @@ -0,0 +1,777 @@ +/* + * An implementation of a simple text chat only messenger on the tox network + * core. + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2013 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifndef MESSENGER_H +#define MESSENGER_H + +#include "friend_connection.h" +#include "friend_requests.h" +#include "logger.h" + +#define MAX_NAME_LENGTH 128 +/* TODO(irungentoo): this must depend on other variable. */ +#define MAX_STATUSMESSAGE_LENGTH 1007 +/* Used for TCP relays in Messenger struct (may need to be % 2 == 0)*/ +#define NUM_SAVED_TCP_RELAYS 8 +/* This cannot be bigger than 256 */ +#define MAX_CONCURRENT_FILE_PIPES 256 + + +#define FRIEND_ADDRESS_SIZE (CRYPTO_PUBLIC_KEY_SIZE + sizeof(uint32_t) + sizeof(uint16_t)) + +enum { + MESSAGE_NORMAL, + MESSAGE_ACTION +}; + +/* NOTE: Packet ids below 24 must never be used. */ +#define PACKET_ID_ONLINE 24 +#define PACKET_ID_OFFLINE 25 +#define PACKET_ID_NICKNAME 48 +#define PACKET_ID_STATUSMESSAGE 49 +#define PACKET_ID_USERSTATUS 50 +#define PACKET_ID_TYPING 51 +#define PACKET_ID_MESSAGE 64 +#define PACKET_ID_ACTION (PACKET_ID_MESSAGE + MESSAGE_ACTION) /* 65 */ +#define PACKET_ID_MSI 69 +#define PACKET_ID_FILE_SENDREQUEST 80 +#define PACKET_ID_FILE_CONTROL 81 +#define PACKET_ID_FILE_DATA 82 +#define PACKET_ID_INVITE_CONFERENCE 96 +#define PACKET_ID_ONLINE_PACKET 97 +#define PACKET_ID_DIRECT_CONFERENCE 98 +#define PACKET_ID_MESSAGE_CONFERENCE 99 +#define PACKET_ID_LOSSY_CONFERENCE 199 + +/* All packets starting with a byte in this range can be used for anything. */ +#define PACKET_ID_LOSSLESS_RANGE_START 160 +#define PACKET_ID_LOSSLESS_RANGE_SIZE 32 +#define PACKET_LOSSY_AV_RESERVED 8 /* Number of lossy packet types at start of range reserved for A/V. */ + +typedef struct { + uint8_t ipv6enabled; + uint8_t udp_disabled; + TCP_Proxy_Info proxy_info; + uint16_t port_range[2]; + uint16_t tcp_server_port; + + uint8_t hole_punching_enabled; + bool local_discovery_enabled; + + logger_cb *log_callback; + void *log_user_data; +} Messenger_Options; + + +struct Receipts { + uint32_t packet_num; + uint32_t msg_id; + struct Receipts *next; +}; + +/* Status definitions. */ +enum { + NOFRIEND, + FRIEND_ADDED, + FRIEND_REQUESTED, + FRIEND_CONFIRMED, + FRIEND_ONLINE, +}; + +/* Errors for m_addfriend + * FAERR - Friend Add Error + */ +enum { + FAERR_TOOLONG = -1, + FAERR_NOMESSAGE = -2, + FAERR_OWNKEY = -3, + FAERR_ALREADYSENT = -4, + FAERR_BADCHECKSUM = -6, + FAERR_SETNEWNOSPAM = -7, + FAERR_NOMEM = -8 +}; + + +/* Default start timeout in seconds between friend requests. */ +#define FRIENDREQUEST_TIMEOUT 5; + +enum { + CONNECTION_NONE, + CONNECTION_TCP, + CONNECTION_UDP, + CONNECTION_UNKNOWN +}; + +/* USERSTATUS - + * Represents userstatuses someone can have. + */ + +typedef enum { + USERSTATUS_NONE, + USERSTATUS_AWAY, + USERSTATUS_BUSY, + USERSTATUS_INVALID +} +USERSTATUS; + +#define FILE_ID_LENGTH 32 + +struct File_Transfers { + uint64_t size; + uint64_t transferred; + uint8_t status; /* 0 == no transfer, 1 = not accepted, 3 = transferring, 4 = broken, 5 = finished */ + uint8_t paused; /* 0: not paused, 1 = paused by us, 2 = paused by other, 3 = paused by both. */ + uint32_t last_packet_number; /* number of the last packet sent. */ + uint64_t requested; /* total data requested by the request chunk callback */ + unsigned int slots_allocated; /* number of slots allocated to this transfer. */ + uint8_t id[FILE_ID_LENGTH]; +}; +enum { + FILESTATUS_NONE, + FILESTATUS_NOT_ACCEPTED, + FILESTATUS_TRANSFERRING, + //FILESTATUS_BROKEN, + FILESTATUS_FINISHED +}; + +enum { + FILE_PAUSE_NOT, + FILE_PAUSE_US, + FILE_PAUSE_OTHER, + FILE_PAUSE_BOTH +}; + +enum { + FILECONTROL_ACCEPT, + FILECONTROL_PAUSE, + FILECONTROL_KILL, + FILECONTROL_SEEK +}; + +enum { + FILEKIND_DATA, + FILEKIND_AVATAR +}; + + +typedef struct Messenger Messenger; + +typedef struct { + uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; + int friendcon_id; + + uint64_t friendrequest_lastsent; // Time at which the last friend request was sent. + uint32_t friendrequest_timeout; // The timeout between successful friendrequest sending attempts. + uint8_t status; // 0 if no friend, 1 if added, 2 if friend request sent, 3 if confirmed friend, 4 if online. + uint8_t info[MAX_FRIEND_REQUEST_DATA_SIZE]; // the data that is sent during the friend requests we do. + uint8_t name[MAX_NAME_LENGTH]; + uint16_t name_length; + uint8_t name_sent; // 0 if we didn't send our name to this friend 1 if we have. + uint8_t statusmessage[MAX_STATUSMESSAGE_LENGTH]; + uint16_t statusmessage_length; + uint8_t statusmessage_sent; + USERSTATUS userstatus; + uint8_t userstatus_sent; + uint8_t user_istyping; + uint8_t user_istyping_sent; + uint8_t is_typing; + uint16_t info_size; // Length of the info. + uint32_t message_id; // a semi-unique id used in read receipts. + uint32_t friendrequest_nospam; // The nospam number used in the friend request. + uint64_t last_seen_time; + uint8_t last_connection_udp_tcp; + struct File_Transfers file_sending[MAX_CONCURRENT_FILE_PIPES]; + unsigned int num_sending_files; + struct File_Transfers file_receiving[MAX_CONCURRENT_FILE_PIPES]; + + struct { + int (*function)(Messenger *m, uint32_t friendnumber, const uint8_t *data, uint16_t len, void *object); + void *object; + } lossy_rtp_packethandlers[PACKET_LOSSY_AV_RESERVED]; + + struct Receipts *receipts_start; + struct Receipts *receipts_end; +} Friend; + +struct Messenger { + Logger *log; + + Networking_Core *net; + Net_Crypto *net_crypto; + DHT *dht; + + Onion *onion; + Onion_Announce *onion_a; + Onion_Client *onion_c; + + Friend_Connections *fr_c; + + TCP_Server *tcp_server; + Friend_Requests fr; + uint8_t name[MAX_NAME_LENGTH]; + uint16_t name_length; + + uint8_t statusmessage[MAX_STATUSMESSAGE_LENGTH]; + uint16_t statusmessage_length; + + USERSTATUS userstatus; + + Friend *friendlist; + uint32_t numfriends; + + time_t lastdump; + + uint8_t has_added_relays; // If the first connection has occurred in do_messenger + Node_format loaded_relays[NUM_SAVED_TCP_RELAYS]; // Relays loaded from config + + void (*friend_message)(struct Messenger *m, uint32_t, unsigned int, const uint8_t *, size_t, void *); + void (*friend_namechange)(struct Messenger *m, uint32_t, const uint8_t *, size_t, void *); + void (*friend_statusmessagechange)(struct Messenger *m, uint32_t, const uint8_t *, size_t, void *); + void (*friend_userstatuschange)(struct Messenger *m, uint32_t, unsigned int, void *); + void (*friend_typingchange)(struct Messenger *m, uint32_t, bool, void *); + void (*read_receipt)(struct Messenger *m, uint32_t, uint32_t, void *); + void (*friend_connectionstatuschange)(struct Messenger *m, uint32_t, unsigned int, void *); + void (*friend_connectionstatuschange_internal)(struct Messenger *m, uint32_t, uint8_t, void *); + void *friend_connectionstatuschange_internal_userdata; + + void *conferences_object; /* Set by new_groupchats()*/ + void (*conference_invite)(struct Messenger *m, uint32_t, const uint8_t *, uint16_t, void *); + + void (*file_sendrequest)(struct Messenger *m, uint32_t, uint32_t, uint32_t, uint64_t, const uint8_t *, size_t, + void *); + void (*file_filecontrol)(struct Messenger *m, uint32_t, uint32_t, unsigned int, void *); + void (*file_filedata)(struct Messenger *m, uint32_t, uint32_t, uint64_t, const uint8_t *, size_t, void *); + void (*file_reqchunk)(struct Messenger *m, uint32_t, uint32_t, uint64_t, size_t, void *); + + void (*msi_packet)(struct Messenger *m, uint32_t, const uint8_t *, uint16_t, void *); + void *msi_packet_userdata; + + void (*lossy_packethandler)(struct Messenger *m, uint32_t, const uint8_t *, size_t, void *); + void (*lossless_packethandler)(struct Messenger *m, uint32_t, const uint8_t *, size_t, void *); + + void (*core_connection_change)(struct Messenger *m, unsigned int, void *); + unsigned int last_connection_status; + + Messenger_Options options; +}; + +/* Format: [real_pk (32 bytes)][nospam number (4 bytes)][checksum (2 bytes)] + * + * return FRIEND_ADDRESS_SIZE byte address to give to others. + */ +void getaddress(const Messenger *m, uint8_t *address); + +/* Add a friend. + * Set the data that will be sent along with friend request. + * address is the address of the friend (returned by getaddress of the friend + * you wish to add) it must be FRIEND_ADDRESS_SIZE bytes. + * TODO(irungentoo): add checksum. + * data is the data and length is the length. + * + * return the friend number if success. + * return -1 if message length is too long. + * return -2 if no message (message length must be >= 1 byte). + * return -3 if user's own key. + * return -4 if friend request already sent or already a friend. + * return -6 if bad checksum in address. + * return -7 if the friend was already there but the nospam was different. + * (the nospam for that friend was set to the new one). + * return -8 if increasing the friend list size fails. + */ +int32_t m_addfriend(Messenger *m, const uint8_t *address, const uint8_t *data, uint16_t length); + + +/* Add a friend without sending a friendrequest. + * return the friend number if success. + * return -3 if user's own key. + * return -4 if friend request already sent or already a friend. + * return -6 if bad checksum in address. + * return -8 if increasing the friend list size fails. + */ +int32_t m_addfriend_norequest(Messenger *m, const uint8_t *real_pk); + +/* return the friend number associated to that client id. + * return -1 if no such friend. + */ +int32_t getfriend_id(const Messenger *m, const uint8_t *real_pk); + +/* Copies the public key associated to that friend id into real_pk buffer. + * Make sure that real_pk is of size CRYPTO_PUBLIC_KEY_SIZE. + * + * return 0 if success + * return -1 if failure + */ +int get_real_pk(const Messenger *m, int32_t friendnumber, uint8_t *real_pk); + +/* return friend connection id on success. + * return -1 if failure. + */ +int getfriendcon_id(const Messenger *m, int32_t friendnumber); + +/* Remove a friend. + * + * return 0 if success + * return -1 if failure + */ +int m_delfriend(Messenger *m, int32_t friendnumber); + +/* Checks friend's connecting status. + * + * return CONNECTION_UDP (2) if friend is directly connected to us (Online UDP). + * return CONNECTION_TCP (1) if friend is connected to us (Online TCP). + * return CONNECTION_NONE (0) if friend is not connected to us (Offline). + * return -1 on failure. + */ +int m_get_friend_connectionstatus(const Messenger *m, int32_t friendnumber); + +/* Checks if there exists a friend with given friendnumber. + * + * return 1 if friend exists. + * return 0 if friend doesn't exist. + */ +int m_friend_exists(const Messenger *m, int32_t friendnumber); + +/* Send a message of type to an online friend. + * + * return -1 if friend not valid. + * return -2 if too large. + * return -3 if friend not online. + * return -4 if send failed (because queue is full). + * return -5 if bad type. + * return 0 if success. + * + * the value in message_id will be passed to your read_receipt callback when the other receives the message. + */ +int m_send_message_generic(Messenger *m, int32_t friendnumber, uint8_t type, const uint8_t *message, uint32_t length, + uint32_t *message_id); + + +/* Set the name and name_length of a friend. + * name must be a string of maximum MAX_NAME_LENGTH length. + * length must be at least 1 byte. + * length is the length of name with the NULL terminator. + * + * return 0 if success. + * return -1 if failure. + */ +int setfriendname(Messenger *m, int32_t friendnumber, const uint8_t *name, uint16_t length); + +/* Set our nickname. + * name must be a string of maximum MAX_NAME_LENGTH length. + * length must be at least 1 byte. + * length is the length of name with the NULL terminator. + * + * return 0 if success. + * return -1 if failure. + */ +int setname(Messenger *m, const uint8_t *name, uint16_t length); + +/* + * Get your nickname. + * m - The messenger context to use. + * name needs to be a valid memory location with a size of at least MAX_NAME_LENGTH bytes. + * + * return length of the name. + * return 0 on error. + */ +uint16_t getself_name(const Messenger *m, uint8_t *name); + +/* 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. + * + * return length of name if success. + * return -1 if failure. + */ +int getname(const Messenger *m, int32_t friendnumber, uint8_t *name); + +/* return the length of name, including null on success. + * return -1 on failure. + */ +int m_get_name_size(const Messenger *m, int32_t friendnumber); +int m_get_self_name_size(const Messenger *m); + +/* Set our user status. + * You are responsible for freeing status after. + * + * returns 0 on success. + * returns -1 on failure. + */ +int m_set_statusmessage(Messenger *m, const uint8_t *status, uint16_t length); +int m_set_userstatus(Messenger *m, uint8_t status); + +/* return the length of friendnumber's status message, including null on success. + * return -1 on failure. + */ +int m_get_statusmessage_size(const Messenger *m, int32_t friendnumber); +int m_get_self_statusmessage_size(const Messenger *m); + +/* 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. + * The self variant will copy our own status message. + * + * returns the length of the copied data on success + * retruns -1 on failure. + */ +int m_copy_statusmessage(const Messenger *m, int32_t friendnumber, uint8_t *buf, uint32_t maxlen); +int m_copy_self_statusmessage(const Messenger *m, uint8_t *buf); + +/* 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. + */ +uint8_t m_get_userstatus(const Messenger *m, int32_t friendnumber); +uint8_t m_get_self_userstatus(const Messenger *m); + + +/* returns timestamp of last time friendnumber was seen online or 0 if never seen. + * if friendnumber is invalid this function will return UINT64_MAX. + */ +uint64_t m_get_last_online(const Messenger *m, int32_t friendnumber); + +/* Set our typing status for a friend. + * You are responsible for turning it on or off. + * + * returns 0 on success. + * returns -1 on failure. + */ +int m_set_usertyping(Messenger *m, int32_t friendnumber, uint8_t is_typing); + +/* Get the typing status of a friend. + * + * returns 0 if friend is not typing. + * returns 1 if friend is typing. + */ +int m_get_istyping(const Messenger *m, int32_t friendnumber); + +/* Set the logger callback. + */ +void m_callback_log(Messenger *m, logger_cb *function, void *context, void *userdata); + +/* Set the function that will be executed when a friend request is received. + * Function format is function(uint8_t * public_key, uint8_t * data, size_t length) + */ +void m_callback_friendrequest(Messenger *m, void (*function)(Messenger *m, const uint8_t *, const uint8_t *, size_t, + void *)); + +/* Set the function that will be executed when a message from a friend is received. + * Function format is: function(uint32_t friendnumber, unsigned int type, uint8_t * message, uint32_t length) + */ +void m_callback_friendmessage(Messenger *m, void (*function)(Messenger *m, uint32_t, unsigned int, const uint8_t *, + size_t, void *)); + +/* Set the callback for name changes. + * Function(uint32_t friendnumber, uint8_t *newname, size_t length) + * You are not responsible for freeing newname. + */ +void m_callback_namechange(Messenger *m, void (*function)(Messenger *m, uint32_t, const uint8_t *, size_t, void *)); + +/* Set the callback for status message changes. + * Function(uint32_t friendnumber, uint8_t *newstatus, size_t length) + * + * You are not responsible for freeing newstatus + */ +void m_callback_statusmessage(Messenger *m, void (*function)(Messenger *m, uint32_t, const uint8_t *, size_t, void *)); + +/* Set the callback for status type changes. + * Function(uint32_t friendnumber, USERSTATUS kind) + */ +void m_callback_userstatus(Messenger *m, void (*function)(Messenger *m, uint32_t, unsigned int, void *)); + +/* Set the callback for typing changes. + * Function(uint32_t friendnumber, uint8_t is_typing) + */ +void m_callback_typingchange(Messenger *m, void(*function)(Messenger *m, uint32_t, bool, void *)); + +/* Set the callback for read receipts. + * Function(uint32_t friendnumber, uint32_t receipt) + * + * If you are keeping a record of returns from m_sendmessage, + * receipt might be one of those values, meaning the message + * has been received on the other side. + * Since core doesn't track ids for you, receipt may not correspond to any message. + * In that case, you should discard it. + */ +void m_callback_read_receipt(Messenger *m, void (*function)(Messenger *m, uint32_t, uint32_t, void *)); + +/* Set the callback for connection status changes. + * function(uint32_t friendnumber, uint8_t status) + * + * Status: + * 0 -- friend went offline after being previously online. + * 1 -- friend went online. + * + * Note that this callback is not called when adding friends, thus the "after + * being previously online" part. + * It's assumed that when adding friends, their connection status is offline. + */ +void m_callback_connectionstatus(Messenger *m, void (*function)(Messenger *m, uint32_t, unsigned int, void *)); + +/* Same as previous but for internal A/V core usage only */ +void m_callback_connectionstatus_internal_av(Messenger *m, void (*function)(Messenger *m, uint32_t, uint8_t, void *), + void *userdata); + + +/* Set the callback for typing changes. + * Function(unsigned int connection_status (0 = not connected, 1 = TCP only, 2 = UDP + TCP)) + */ +void m_callback_core_connection(Messenger *m, void (*function)(Messenger *m, unsigned int, void *)); + +/**********CONFERENCES************/ + +/* Set the callback for conference invites. + * + * Function(Messenger *m, uint32_t friendnumber, uint8_t *data, uint16_t length, void *userdata) + */ +void m_callback_conference_invite(Messenger *m, void (*function)(Messenger *m, uint32_t, const uint8_t *, uint16_t, + void *)); + +/* Send a conference invite packet. + * + * return 1 on success + * return 0 on failure + */ +int send_conference_invite_packet(const Messenger *m, int32_t friendnumber, const uint8_t *data, uint16_t length); + +/****************FILE SENDING*****************/ + + +/* Set the callback for file send requests. + * + * Function(Tox *tox, uint32_t friendnumber, uint32_t filenumber, uint32_t filetype, uint64_t filesize, uint8_t *filename, size_t filename_length, void *userdata) + */ +void callback_file_sendrequest(Messenger *m, void (*function)(Messenger *m, uint32_t, uint32_t, uint32_t, uint64_t, + const uint8_t *, size_t, void *)); + + +/* Set the callback for file control requests. + * + * Function(Tox *tox, uint32_t friendnumber, uint32_t filenumber, unsigned int control_type, void *userdata) + * + */ +void callback_file_control(Messenger *m, void (*function)(Messenger *m, uint32_t, uint32_t, unsigned int, void *)); + +/* Set the callback for file data. + * + * Function(Tox *tox, uint32_t friendnumber, uint32_t filenumber, uint64_t position, uint8_t *data, size_t length, void *userdata) + * + */ +void callback_file_data(Messenger *m, void (*function)(Messenger *m, uint32_t, uint32_t, uint64_t, const uint8_t *, + size_t, void *)); + +/* Set the callback for file request chunk. + * + * Function(Tox *tox, uint32_t friendnumber, uint32_t filenumber, uint64_t position, size_t length, void *userdata) + * + */ +void callback_file_reqchunk(Messenger *m, void (*function)(Messenger *m, uint32_t, uint32_t, uint64_t, size_t, void *)); + + +/* Copy the file transfer file id to file_id + * + * return 0 on success. + * return -1 if friend not valid. + * return -2 if filenumber not valid + */ +int file_get_id(const Messenger *m, int32_t friendnumber, uint32_t filenumber, uint8_t *file_id); + +/* Send a file send request. + * Maximum filename length is 255 bytes. + * return file number on success + * return -1 if friend not found. + * return -2 if filename length invalid. + * return -3 if no more file sending slots left. + * return -4 if could not send packet (friend offline). + * + */ +long int new_filesender(const Messenger *m, int32_t friendnumber, uint32_t file_type, uint64_t filesize, + const uint8_t *file_id, const uint8_t *filename, uint16_t filename_length); + +/* Send a file control request. + * + * return 0 on success + * return -1 if friend not valid. + * return -2 if friend not online. + * return -3 if file number invalid. + * return -4 if file control is bad. + * return -5 if file already paused. + * return -6 if resume file failed because it was only paused by the other. + * return -7 if resume file failed because it wasn't paused. + * return -8 if packet failed to send. + */ +int file_control(const Messenger *m, int32_t friendnumber, uint32_t filenumber, unsigned int control); + +/* Send a seek file control request. + * + * return 0 on success + * return -1 if friend not valid. + * return -2 if friend not online. + * return -3 if file number invalid. + * return -4 if not receiving file. + * return -5 if file status wrong. + * return -6 if position bad. + * return -8 if packet failed to send. + */ +int file_seek(const Messenger *m, int32_t friendnumber, uint32_t filenumber, uint64_t position); + +/* Send file data. + * + * return 0 on success + * return -1 if friend not valid. + * return -2 if friend not online. + * return -3 if filenumber invalid. + * return -4 if file transfer not transferring. + * return -5 if bad data size. + * return -6 if packet queue full. + * return -7 if wrong position. + */ +int file_data(const Messenger *m, int32_t friendnumber, uint32_t filenumber, uint64_t position, const uint8_t *data, + uint16_t length); + +/* Give the number of bytes left to be sent/received. + * + * send_receive is 0 if we want the sending files, 1 if we want the receiving. + * + * return number of bytes remaining to be sent/received on success + * return 0 on failure + */ +uint64_t file_dataremaining(const Messenger *m, int32_t friendnumber, uint8_t filenumber, uint8_t send_receive); + +/*************** A/V related ******************/ + +/* Set the callback for msi packets. + * + * Function(Messenger *m, uint32_t friendnumber, uint8_t *data, uint16_t length, void *userdata) + */ +void m_callback_msi_packet(Messenger *m, void (*function)(Messenger *m, uint32_t, const uint8_t *, uint16_t, void *), + void *userdata); + +/* Send an msi packet. + * + * return 1 on success + * return 0 on failure + */ +int m_msi_packet(const Messenger *m, int32_t friendnumber, const uint8_t *data, uint16_t length); + +/* Set handlers for lossy rtp packets. + * + * return -1 on failure. + * return 0 on success. + */ +int m_callback_rtp_packet(Messenger *m, int32_t friendnumber, uint8_t byte, int (*packet_handler_callback)(Messenger *m, + uint32_t friendnumber, const uint8_t *data, uint16_t len, void *object), void *object); + +/**********************************************/ + +/* Set handlers for custom lossy packets. + * + */ +void custom_lossy_packet_registerhandler(Messenger *m, void (*packet_handler_callback)(Messenger *m, + uint32_t friendnumber, const uint8_t *data, size_t len, void *object)); + +/* High level function to send custom lossy packets. + * + * return -1 if friend invalid. + * return -2 if length wrong. + * return -3 if first byte invalid. + * return -4 if friend offline. + * return -5 if packet failed to send because of other error. + * return 0 on success. + */ +int m_send_custom_lossy_packet(const Messenger *m, int32_t friendnumber, const uint8_t *data, uint32_t length); + + +/* Set handlers for custom lossless packets. + * + */ +void custom_lossless_packet_registerhandler(Messenger *m, void (*packet_handler_callback)(Messenger *m, + uint32_t friendnumber, const uint8_t *data, size_t len, void *object)); + +/* High level function to send custom lossless packets. + * + * return -1 if friend invalid. + * return -2 if length wrong. + * return -3 if first byte invalid. + * return -4 if friend offline. + * return -5 if packet failed to send because of other error. + * return 0 on success. + */ +int send_custom_lossless_packet(const Messenger *m, int32_t friendnumber, const uint8_t *data, uint32_t length); + +/**********************************************/ + +enum { + MESSENGER_ERROR_NONE, + MESSENGER_ERROR_PORT, + MESSENGER_ERROR_TCP_SERVER, + MESSENGER_ERROR_OTHER +}; + +/* Run this at startup. + * return allocated instance of Messenger on success. + * return 0 if there are problems. + * + * if error is not NULL it will be set to one of the values in the enum above. + */ +Messenger *new_messenger(Messenger_Options *options, unsigned int *error); + +/* Run this before closing shop + * Free all datastructures. + */ +void kill_messenger(Messenger *m); + +/* The main loop that needs to be run at least 20 times per second. */ +void do_messenger(Messenger *m, void *userdata); + +/* Return the time in milliseconds before do_messenger() should be called again + * for optimal performance. + * + * returns time (in ms) before the next do_messenger() needs to be run on success. + */ +uint32_t messenger_run_interval(const Messenger *m); + +/* SAVING AND LOADING FUNCTIONS: */ + +/* return size of the messenger data (for saving). */ +uint32_t messenger_size(const Messenger *m); + +/* Save the messenger in data (must be allocated memory of size Messenger_size()) */ +void messenger_save(const Messenger *m, uint8_t *data); + +/* Load the messenger from data of size length. */ +int messenger_load(Messenger *m, const uint8_t *data, uint32_t length); + +/* Return the number of friends in the instance m. + * You should use this to determine how much memory to allocate + * for copy_friendlist. */ +uint32_t count_friendlist(const Messenger *m); + +/* Copy a list of valid friend 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_friendlist(const Messenger *m, uint32_t *out_list, uint32_t list_size); + +#endif diff --git a/libs/libtox/src/toxcore/TCP_client.c b/libs/libtox/src/toxcore/TCP_client.c new file mode 100644 index 0000000000..8a14c7cd70 --- /dev/null +++ b/libs/libtox/src/toxcore/TCP_client.c @@ -0,0 +1,991 @@ +/* + * Implementation of the TCP relay client part of Tox. + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2014 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "TCP_client.h" + +#include "util.h" + +#if !defined(_WIN32) && !defined(__WIN32__) && !defined (WIN32) +#include <sys/ioctl.h> +#endif + +/* return 1 on success + * return 0 on failure + */ +static int connect_sock_to(Socket sock, IP_Port ip_port, TCP_Proxy_Info *proxy_info) +{ + if (proxy_info->proxy_type != TCP_PROXY_NONE) { + ip_port = proxy_info->ip_port; + } + + /* nonblocking socket, connect will never return success */ + net_connect(sock, ip_port); + return 1; +} + +/* return 1 on success. + * return 0 on failure. + */ +static int proxy_http_generate_connection_request(TCP_Client_Connection *TCP_conn) +{ + char one[] = "CONNECT "; + char two[] = " HTTP/1.1\nHost: "; + char three[] = "\r\n\r\n"; + + char ip[TOX_INET6_ADDRSTRLEN]; + + if (!ip_parse_addr(&TCP_conn->ip_port.ip, ip, sizeof(ip))) { + return 0; + } + + const uint16_t port = net_ntohs(TCP_conn->ip_port.port); + const int written = snprintf((char *)TCP_conn->last_packet, MAX_PACKET_SIZE, "%s%s:%hu%s%s:%hu%s", one, ip, port, two, + ip, port, three); + + if (written < 0 || MAX_PACKET_SIZE < written) { + return 0; + } + + TCP_conn->last_packet_length = written; + TCP_conn->last_packet_sent = 0; + + return 1; +} + +/* return 1 on success. + * return 0 if no data received. + * return -1 on failure (connection refused). + */ +static int proxy_http_read_connection_response(TCP_Client_Connection *TCP_conn) +{ + char success[] = "200"; + uint8_t data[16]; // draining works the best if the length is a power of 2 + + int ret = read_TCP_packet(TCP_conn->sock, data, sizeof(data) - 1); + + if (ret == -1) { + return 0; + } + + data[sizeof(data) - 1] = 0; + + if (strstr((char *)data, success)) { + // drain all data + unsigned int data_left = TCP_socket_data_recv_buffer(TCP_conn->sock); + + if (data_left) { + VLA(uint8_t, temp_data, data_left); + read_TCP_packet(TCP_conn->sock, temp_data, data_left); + } + + return 1; + } + + return -1; +} + +static void proxy_socks5_generate_handshake(TCP_Client_Connection *TCP_conn) +{ + TCP_conn->last_packet[0] = 5; /* SOCKSv5 */ + TCP_conn->last_packet[1] = 1; /* number of authentication methods supported */ + TCP_conn->last_packet[2] = 0; /* No authentication */ + + TCP_conn->last_packet_length = 3; + TCP_conn->last_packet_sent = 0; +} + +/* return 1 on success. + * return 0 if no data received. + * return -1 on failure (connection refused). + */ +static int socks5_read_handshake_response(TCP_Client_Connection *TCP_conn) +{ + uint8_t data[2]; + int ret = read_TCP_packet(TCP_conn->sock, data, sizeof(data)); + + if (ret == -1) { + return 0; + } + + if (data[0] == 5 && data[1] == 0) { // TODO(irungentoo): magic numbers + return 1; + } + + return -1; +} + +static void proxy_socks5_generate_connection_request(TCP_Client_Connection *TCP_conn) +{ + TCP_conn->last_packet[0] = 5; /* SOCKSv5 */ + TCP_conn->last_packet[1] = 1; /* command code: establish a TCP/IP stream connection */ + TCP_conn->last_packet[2] = 0; /* reserved, must be 0 */ + uint16_t length = 3; + + if (TCP_conn->ip_port.ip.family == TOX_AF_INET) { + TCP_conn->last_packet[3] = 1; /* IPv4 address */ + ++length; + memcpy(TCP_conn->last_packet + length, TCP_conn->ip_port.ip.ip4.uint8, sizeof(IP4)); + length += sizeof(IP4); + } else { + TCP_conn->last_packet[3] = 4; /* IPv6 address */ + ++length; + memcpy(TCP_conn->last_packet + length, TCP_conn->ip_port.ip.ip6.uint8, sizeof(IP6)); + length += sizeof(IP6); + } + + memcpy(TCP_conn->last_packet + length, &TCP_conn->ip_port.port, sizeof(uint16_t)); + length += sizeof(uint16_t); + + TCP_conn->last_packet_length = length; + TCP_conn->last_packet_sent = 0; +} + +/* return 1 on success. + * return 0 if no data received. + * return -1 on failure (connection refused). + */ +static int proxy_socks5_read_connection_response(TCP_Client_Connection *TCP_conn) +{ + if (TCP_conn->ip_port.ip.family == TOX_AF_INET) { + uint8_t data[4 + sizeof(IP4) + sizeof(uint16_t)]; + int ret = read_TCP_packet(TCP_conn->sock, data, sizeof(data)); + + if (ret == -1) { + return 0; + } + + if (data[0] == 5 && data[1] == 0) { + return 1; + } + } else { + uint8_t data[4 + sizeof(IP6) + sizeof(uint16_t)]; + int ret = read_TCP_packet(TCP_conn->sock, data, sizeof(data)); + + if (ret == -1) { + return 0; + } + + if (data[0] == 5 && data[1] == 0) { + return 1; + } + } + + return -1; +} + +/* return 0 on success. + * return -1 on failure. + */ +static int generate_handshake(TCP_Client_Connection *TCP_conn) +{ + uint8_t plain[CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE]; + crypto_new_keypair(plain, TCP_conn->temp_secret_key); + random_nonce(TCP_conn->sent_nonce); + memcpy(plain + CRYPTO_PUBLIC_KEY_SIZE, TCP_conn->sent_nonce, CRYPTO_NONCE_SIZE); + memcpy(TCP_conn->last_packet, TCP_conn->self_public_key, CRYPTO_PUBLIC_KEY_SIZE); + random_nonce(TCP_conn->last_packet + CRYPTO_PUBLIC_KEY_SIZE); + int len = encrypt_data_symmetric(TCP_conn->shared_key, TCP_conn->last_packet + CRYPTO_PUBLIC_KEY_SIZE, plain, + sizeof(plain), TCP_conn->last_packet + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE); + + if (len != sizeof(plain) + CRYPTO_MAC_SIZE) { + return -1; + } + + TCP_conn->last_packet_length = CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + sizeof(plain) + CRYPTO_MAC_SIZE; + TCP_conn->last_packet_sent = 0; + return 0; +} + +/* data must be of length TCP_SERVER_HANDSHAKE_SIZE + * + * return 0 on success. + * return -1 on failure. + */ +static int handle_handshake(TCP_Client_Connection *TCP_conn, const uint8_t *data) +{ + uint8_t plain[CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE]; + int len = decrypt_data_symmetric(TCP_conn->shared_key, data, data + CRYPTO_NONCE_SIZE, + TCP_SERVER_HANDSHAKE_SIZE - CRYPTO_NONCE_SIZE, plain); + + if (len != sizeof(plain)) { + return -1; + } + + memcpy(TCP_conn->recv_nonce, plain + CRYPTO_PUBLIC_KEY_SIZE, CRYPTO_NONCE_SIZE); + encrypt_precompute(plain, TCP_conn->temp_secret_key, TCP_conn->shared_key); + crypto_memzero(TCP_conn->temp_secret_key, CRYPTO_SECRET_KEY_SIZE); + return 0; +} + +/* return 0 if pending data was sent completely + * return -1 if it wasn't + */ +static int client_send_pending_data_nonpriority(TCP_Client_Connection *con) +{ + if (con->last_packet_length == 0) { + return 0; + } + + uint16_t left = con->last_packet_length - con->last_packet_sent; + const char *data = (const char *)(con->last_packet + con->last_packet_sent); + int len = send(con->sock, data, left, MSG_NOSIGNAL); + + if (len <= 0) { + return -1; + } + + if (len == left) { + con->last_packet_length = 0; + con->last_packet_sent = 0; + return 0; + } + + con->last_packet_sent += len; + return -1; +} + +/* return 0 if pending data was sent completely + * return -1 if it wasn't + */ +static int client_send_pending_data(TCP_Client_Connection *con) +{ + /* finish sending current non-priority packet */ + if (client_send_pending_data_nonpriority(con) == -1) { + return -1; + } + + TCP_Priority_List *p = con->priority_queue_start; + + while (p) { + uint16_t left = p->size - p->sent; + int len = send(con->sock, (const char *)(p->data + p->sent), left, MSG_NOSIGNAL); + + if (len != left) { + if (len > 0) { + p->sent += len; + } + + break; + } + + TCP_Priority_List *pp = p; + p = p->next; + free(pp); + } + + con->priority_queue_start = p; + + if (!p) { + con->priority_queue_end = NULL; + return 0; + } + + return -1; +} + +/* return 0 on failure (only if malloc fails) + * return 1 on success + */ +static bool client_add_priority(TCP_Client_Connection *con, const uint8_t *packet, uint16_t size, uint16_t sent) +{ + TCP_Priority_List *p = con->priority_queue_end; + TCP_Priority_List *new_list = (TCP_Priority_List *)malloc(sizeof(TCP_Priority_List) + size); + + if (!new_list) { + return 0; + } + + new_list->next = NULL; + new_list->size = size; + new_list->sent = sent; + memcpy(new_list->data, packet, size); + + if (p) { + p->next = new_list; + } else { + con->priority_queue_start = new_list; + } + + con->priority_queue_end = new_list; + return 1; +} + +static void wipe_priority_list(TCP_Client_Connection *con) +{ + TCP_Priority_List *p = con->priority_queue_start; + + while (p) { + TCP_Priority_List *pp = p; + p = p->next; + free(pp); + } +} + +/* return 1 on success. + * return 0 if could not send packet. + * return -1 on failure (connection must be killed). + */ +static int write_packet_TCP_client_secure_connection(TCP_Client_Connection *con, const uint8_t *data, uint16_t length, + bool priority) +{ + if (length + CRYPTO_MAC_SIZE > MAX_PACKET_SIZE) { + return -1; + } + + bool sendpriority = 1; + + if (client_send_pending_data(con) == -1) { + if (priority) { + sendpriority = 0; + } else { + return 0; + } + } + + VLA(uint8_t, packet, sizeof(uint16_t) + length + CRYPTO_MAC_SIZE); + + uint16_t c_length = net_htons(length + CRYPTO_MAC_SIZE); + memcpy(packet, &c_length, sizeof(uint16_t)); + int len = encrypt_data_symmetric(con->shared_key, con->sent_nonce, data, length, packet + sizeof(uint16_t)); + + if ((unsigned int)len != (SIZEOF_VLA(packet) - sizeof(uint16_t))) { + return -1; + } + + if (priority) { + len = sendpriority ? send(con->sock, (const char *)packet, SIZEOF_VLA(packet), MSG_NOSIGNAL) : 0; + + if (len <= 0) { + len = 0; + } + + increment_nonce(con->sent_nonce); + + if ((unsigned int)len == SIZEOF_VLA(packet)) { + return 1; + } + + return client_add_priority(con, packet, SIZEOF_VLA(packet), len); + } + + len = send(con->sock, (const char *)packet, SIZEOF_VLA(packet), MSG_NOSIGNAL); + + if (len <= 0) { + return 0; + } + + increment_nonce(con->sent_nonce); + + if ((unsigned int)len == SIZEOF_VLA(packet)) { + return 1; + } + + memcpy(con->last_packet, packet, SIZEOF_VLA(packet)); + con->last_packet_length = SIZEOF_VLA(packet); + con->last_packet_sent = len; + return 1; +} + +/* return 1 on success. + * return 0 if could not send packet. + * return -1 on failure (connection must be killed). + */ +int send_routing_request(TCP_Client_Connection *con, uint8_t *public_key) +{ + uint8_t packet[1 + CRYPTO_PUBLIC_KEY_SIZE]; + packet[0] = TCP_PACKET_ROUTING_REQUEST; + memcpy(packet + 1, public_key, CRYPTO_PUBLIC_KEY_SIZE); + return write_packet_TCP_client_secure_connection(con, packet, sizeof(packet), 1); +} + +void routing_response_handler(TCP_Client_Connection *con, int (*response_callback)(void *object, uint8_t connection_id, + const uint8_t *public_key), void *object) +{ + con->response_callback = response_callback; + con->response_callback_object = object; +} + +void routing_status_handler(TCP_Client_Connection *con, int (*status_callback)(void *object, uint32_t number, + uint8_t connection_id, uint8_t status), void *object) +{ + con->status_callback = status_callback; + con->status_callback_object = object; +} + +static int tcp_send_ping_response(TCP_Client_Connection *con); +static int tcp_send_ping_request(TCP_Client_Connection *con); + +/* return 1 on success. + * return 0 if could not send packet. + * return -1 on failure. + */ +int send_data(TCP_Client_Connection *con, uint8_t con_id, const uint8_t *data, uint16_t length) +{ + if (con_id >= NUM_CLIENT_CONNECTIONS) { + return -1; + } + + if (con->connections[con_id].status != 2) { + return -1; + } + + if (tcp_send_ping_response(con) == 0 || tcp_send_ping_request(con) == 0) { + return 0; + } + + VLA(uint8_t, packet, 1 + length); + packet[0] = con_id + NUM_RESERVED_PORTS; + memcpy(packet + 1, data, length); + return write_packet_TCP_client_secure_connection(con, packet, SIZEOF_VLA(packet), 0); +} + +/* return 1 on success. + * return 0 if could not send packet. + * return -1 on failure. + */ +int send_oob_packet(TCP_Client_Connection *con, const uint8_t *public_key, const uint8_t *data, uint16_t length) +{ + if (length == 0 || length > TCP_MAX_OOB_DATA_LENGTH) { + return -1; + } + + VLA(uint8_t, packet, 1 + CRYPTO_PUBLIC_KEY_SIZE + length); + packet[0] = TCP_PACKET_OOB_SEND; + memcpy(packet + 1, public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, data, length); + return write_packet_TCP_client_secure_connection(con, packet, SIZEOF_VLA(packet), 0); +} + + +/* Set the number that will be used as an argument in the callbacks related to con_id. + * + * When not set by this function, the number is ~0. + * + * return 0 on success. + * return -1 on failure. + */ +int set_tcp_connection_number(TCP_Client_Connection *con, uint8_t con_id, uint32_t number) +{ + if (con_id >= NUM_CLIENT_CONNECTIONS) { + return -1; + } + + if (con->connections[con_id].status == 0) { + return -1; + } + + con->connections[con_id].number = number; + return 0; +} + +void routing_data_handler(TCP_Client_Connection *con, int (*data_callback)(void *object, uint32_t number, + uint8_t connection_id, const uint8_t *data, uint16_t length, void *userdata), void *object) +{ + con->data_callback = data_callback; + con->data_callback_object = object; +} + +void oob_data_handler(TCP_Client_Connection *con, int (*oob_data_callback)(void *object, const uint8_t *public_key, + const uint8_t *data, uint16_t length, void *userdata), void *object) +{ + con->oob_data_callback = oob_data_callback; + con->oob_data_callback_object = object; +} + +/* return 1 on success. + * return 0 if could not send packet. + * return -1 on failure (connection must be killed). + */ +static int client_send_disconnect_notification(TCP_Client_Connection *con, uint8_t id) +{ + uint8_t packet[1 + 1]; + packet[0] = TCP_PACKET_DISCONNECT_NOTIFICATION; + packet[1] = id; + return write_packet_TCP_client_secure_connection(con, packet, sizeof(packet), 1); +} + +/* return 1 on success. + * return 0 if could not send packet. + * return -1 on failure (connection must be killed). + */ +static int tcp_send_ping_request(TCP_Client_Connection *con) +{ + if (!con->ping_request_id) { + return 1; + } + + uint8_t packet[1 + sizeof(uint64_t)]; + packet[0] = TCP_PACKET_PING; + memcpy(packet + 1, &con->ping_request_id, sizeof(uint64_t)); + int ret; + + if ((ret = write_packet_TCP_client_secure_connection(con, packet, sizeof(packet), 1)) == 1) { + con->ping_request_id = 0; + } + + return ret; +} + +/* return 1 on success. + * return 0 if could not send packet. + * return -1 on failure (connection must be killed). + */ +static int tcp_send_ping_response(TCP_Client_Connection *con) +{ + if (!con->ping_response_id) { + return 1; + } + + uint8_t packet[1 + sizeof(uint64_t)]; + packet[0] = TCP_PACKET_PONG; + memcpy(packet + 1, &con->ping_response_id, sizeof(uint64_t)); + int ret; + + if ((ret = write_packet_TCP_client_secure_connection(con, packet, sizeof(packet), 1)) == 1) { + con->ping_response_id = 0; + } + + return ret; +} + +/* return 1 on success. + * return 0 if could not send packet. + * return -1 on failure (connection must be killed). + */ +int send_disconnect_request(TCP_Client_Connection *con, uint8_t con_id) +{ + if (con_id >= NUM_CLIENT_CONNECTIONS) { + return -1; + } + + con->connections[con_id].status = 0; + con->connections[con_id].number = 0; + return client_send_disconnect_notification(con, con_id + NUM_RESERVED_PORTS); +} + +/* return 1 on success. + * return 0 if could not send packet. + * return -1 on failure (connection must be killed). + */ +int send_onion_request(TCP_Client_Connection *con, const uint8_t *data, uint16_t length) +{ + VLA(uint8_t, packet, 1 + length); + packet[0] = TCP_PACKET_ONION_REQUEST; + memcpy(packet + 1, data, length); + return write_packet_TCP_client_secure_connection(con, packet, SIZEOF_VLA(packet), 0); +} + +void onion_response_handler(TCP_Client_Connection *con, int (*onion_callback)(void *object, const uint8_t *data, + uint16_t length, void *userdata), void *object) +{ + con->onion_callback = onion_callback; + con->onion_callback_object = object; +} + +/* Create new TCP connection to ip_port/public_key + */ +TCP_Client_Connection *new_TCP_connection(IP_Port ip_port, const uint8_t *public_key, const uint8_t *self_public_key, + const uint8_t *self_secret_key, TCP_Proxy_Info *proxy_info) +{ + if (networking_at_startup() != 0) { + return NULL; + } + + if (ip_port.ip.family != TOX_AF_INET && ip_port.ip.family != TOX_AF_INET6) { + return NULL; + } + + TCP_Proxy_Info default_proxyinfo; + + if (proxy_info == NULL) { + default_proxyinfo.proxy_type = TCP_PROXY_NONE; + proxy_info = &default_proxyinfo; + } + + uint8_t family = ip_port.ip.family; + + if (proxy_info->proxy_type != TCP_PROXY_NONE) { + family = proxy_info->ip_port.ip.family; + } + + Socket sock = net_socket(family, TOX_SOCK_STREAM, TOX_PROTO_TCP); + + if (!sock_valid(sock)) { + return NULL; + } + + if (!set_socket_nosigpipe(sock)) { + kill_sock(sock); + return 0; + } + + if (!(set_socket_nonblock(sock) && connect_sock_to(sock, ip_port, proxy_info))) { + kill_sock(sock); + return NULL; + } + + TCP_Client_Connection *temp = (TCP_Client_Connection *)calloc(sizeof(TCP_Client_Connection), 1); + + if (temp == NULL) { + kill_sock(sock); + return NULL; + } + + temp->sock = sock; + memcpy(temp->public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(temp->self_public_key, self_public_key, CRYPTO_PUBLIC_KEY_SIZE); + encrypt_precompute(temp->public_key, self_secret_key, temp->shared_key); + temp->ip_port = ip_port; + temp->proxy_info = *proxy_info; + + switch (proxy_info->proxy_type) { + case TCP_PROXY_HTTP: + temp->status = TCP_CLIENT_PROXY_HTTP_CONNECTING; + proxy_http_generate_connection_request(temp); + break; + + case TCP_PROXY_SOCKS5: + temp->status = TCP_CLIENT_PROXY_SOCKS5_CONNECTING; + proxy_socks5_generate_handshake(temp); + break; + + case TCP_PROXY_NONE: + temp->status = TCP_CLIENT_CONNECTING; + + if (generate_handshake(temp) == -1) { + kill_sock(sock); + free(temp); + return NULL; + } + + break; + } + + temp->kill_at = unix_time() + TCP_CONNECTION_TIMEOUT; + + return temp; +} + +/* return 0 on success + * return -1 on failure + */ +static int handle_TCP_client_packet(TCP_Client_Connection *conn, const uint8_t *data, uint16_t length, void *userdata) +{ + if (length <= 1) { + return -1; + } + + switch (data[0]) { + case TCP_PACKET_ROUTING_RESPONSE: { + if (length != 1 + 1 + CRYPTO_PUBLIC_KEY_SIZE) { + return -1; + } + + if (data[1] < NUM_RESERVED_PORTS) { + return 0; + } + + uint8_t con_id = data[1] - NUM_RESERVED_PORTS; + + if (conn->connections[con_id].status != 0) { + return 0; + } + + conn->connections[con_id].status = 1; + conn->connections[con_id].number = ~0; + memcpy(conn->connections[con_id].public_key, data + 2, CRYPTO_PUBLIC_KEY_SIZE); + + if (conn->response_callback) { + conn->response_callback(conn->response_callback_object, con_id, conn->connections[con_id].public_key); + } + + return 0; + } + + case TCP_PACKET_CONNECTION_NOTIFICATION: { + if (length != 1 + 1) { + return -1; + } + + if (data[1] < NUM_RESERVED_PORTS) { + return -1; + } + + uint8_t con_id = data[1] - NUM_RESERVED_PORTS; + + if (conn->connections[con_id].status != 1) { + return 0; + } + + conn->connections[con_id].status = 2; + + if (conn->status_callback) { + conn->status_callback(conn->status_callback_object, conn->connections[con_id].number, con_id, + conn->connections[con_id].status); + } + + return 0; + } + + case TCP_PACKET_DISCONNECT_NOTIFICATION: { + if (length != 1 + 1) { + return -1; + } + + if (data[1] < NUM_RESERVED_PORTS) { + return -1; + } + + uint8_t con_id = data[1] - NUM_RESERVED_PORTS; + + if (conn->connections[con_id].status == 0) { + return 0; + } + + if (conn->connections[con_id].status != 2) { + return 0; + } + + conn->connections[con_id].status = 1; + + if (conn->status_callback) { + conn->status_callback(conn->status_callback_object, conn->connections[con_id].number, con_id, + conn->connections[con_id].status); + } + + return 0; + } + + case TCP_PACKET_PING: { + if (length != 1 + sizeof(uint64_t)) { + return -1; + } + + uint64_t ping_id; + memcpy(&ping_id, data + 1, sizeof(uint64_t)); + conn->ping_response_id = ping_id; + tcp_send_ping_response(conn); + return 0; + } + + case TCP_PACKET_PONG: { + if (length != 1 + sizeof(uint64_t)) { + return -1; + } + + uint64_t ping_id; + memcpy(&ping_id, data + 1, sizeof(uint64_t)); + + if (ping_id) { + if (ping_id == conn->ping_id) { + conn->ping_id = 0; + } + + return 0; + } + + return -1; + } + + case TCP_PACKET_OOB_RECV: { + if (length <= 1 + CRYPTO_PUBLIC_KEY_SIZE) { + return -1; + } + + if (conn->oob_data_callback) { + conn->oob_data_callback(conn->oob_data_callback_object, data + 1, data + 1 + CRYPTO_PUBLIC_KEY_SIZE, + length - (1 + CRYPTO_PUBLIC_KEY_SIZE), userdata); + } + + return 0; + } + + case TCP_PACKET_ONION_RESPONSE: { + conn->onion_callback(conn->onion_callback_object, data + 1, length - 1, userdata); + return 0; + } + + default: { + if (data[0] < NUM_RESERVED_PORTS) { + return -1; + } + + uint8_t con_id = data[0] - NUM_RESERVED_PORTS; + + if (conn->data_callback) { + conn->data_callback(conn->data_callback_object, conn->connections[con_id].number, con_id, data + 1, length - 1, + userdata); + } + } + } + + return 0; +} + +static int do_confirmed_TCP(TCP_Client_Connection *conn, void *userdata) +{ + client_send_pending_data(conn); + tcp_send_ping_response(conn); + tcp_send_ping_request(conn); + + uint8_t packet[MAX_PACKET_SIZE]; + int len; + + if (is_timeout(conn->last_pinged, TCP_PING_FREQUENCY)) { + uint64_t ping_id = random_64b(); + + if (!ping_id) { + ++ping_id; + } + + conn->ping_request_id = conn->ping_id = ping_id; + tcp_send_ping_request(conn); + conn->last_pinged = unix_time(); + } + + if (conn->ping_id && is_timeout(conn->last_pinged, TCP_PING_TIMEOUT)) { + conn->status = TCP_CLIENT_DISCONNECTED; + return 0; + } + + while ((len = read_packet_TCP_secure_connection(conn->sock, &conn->next_packet_length, conn->shared_key, + conn->recv_nonce, packet, sizeof(packet)))) { + if (len == -1) { + conn->status = TCP_CLIENT_DISCONNECTED; + break; + } + + if (handle_TCP_client_packet(conn, packet, len, userdata) == -1) { + conn->status = TCP_CLIENT_DISCONNECTED; + break; + } + } + + return 0; +} + +/* Run the TCP connection + */ +void do_TCP_connection(TCP_Client_Connection *TCP_connection, void *userdata) +{ + unix_time_update(); + + if (TCP_connection->status == TCP_CLIENT_DISCONNECTED) { + return; + } + + if (TCP_connection->status == TCP_CLIENT_PROXY_HTTP_CONNECTING) { + if (client_send_pending_data(TCP_connection) == 0) { + int ret = proxy_http_read_connection_response(TCP_connection); + + if (ret == -1) { + TCP_connection->kill_at = 0; + TCP_connection->status = TCP_CLIENT_DISCONNECTED; + } + + if (ret == 1) { + generate_handshake(TCP_connection); + TCP_connection->status = TCP_CLIENT_CONNECTING; + } + } + } + + if (TCP_connection->status == TCP_CLIENT_PROXY_SOCKS5_CONNECTING) { + if (client_send_pending_data(TCP_connection) == 0) { + int ret = socks5_read_handshake_response(TCP_connection); + + if (ret == -1) { + TCP_connection->kill_at = 0; + TCP_connection->status = TCP_CLIENT_DISCONNECTED; + } + + if (ret == 1) { + proxy_socks5_generate_connection_request(TCP_connection); + TCP_connection->status = TCP_CLIENT_PROXY_SOCKS5_UNCONFIRMED; + } + } + } + + if (TCP_connection->status == TCP_CLIENT_PROXY_SOCKS5_UNCONFIRMED) { + if (client_send_pending_data(TCP_connection) == 0) { + int ret = proxy_socks5_read_connection_response(TCP_connection); + + if (ret == -1) { + TCP_connection->kill_at = 0; + TCP_connection->status = TCP_CLIENT_DISCONNECTED; + } + + if (ret == 1) { + generate_handshake(TCP_connection); + TCP_connection->status = TCP_CLIENT_CONNECTING; + } + } + } + + if (TCP_connection->status == TCP_CLIENT_CONNECTING) { + if (client_send_pending_data(TCP_connection) == 0) { + TCP_connection->status = TCP_CLIENT_UNCONFIRMED; + } + } + + if (TCP_connection->status == TCP_CLIENT_UNCONFIRMED) { + uint8_t data[TCP_SERVER_HANDSHAKE_SIZE]; + int len = read_TCP_packet(TCP_connection->sock, data, sizeof(data)); + + if (sizeof(data) == len) { + if (handle_handshake(TCP_connection, data) == 0) { + TCP_connection->kill_at = ~0; + TCP_connection->status = TCP_CLIENT_CONFIRMED; + } else { + TCP_connection->kill_at = 0; + TCP_connection->status = TCP_CLIENT_DISCONNECTED; + } + } + } + + if (TCP_connection->status == TCP_CLIENT_CONFIRMED) { + do_confirmed_TCP(TCP_connection, userdata); + } + + if (TCP_connection->kill_at <= unix_time()) { + TCP_connection->status = TCP_CLIENT_DISCONNECTED; + } +} + +/* Kill the TCP connection + */ +void kill_TCP_connection(TCP_Client_Connection *TCP_connection) +{ + if (TCP_connection == NULL) { + return; + } + + wipe_priority_list(TCP_connection); + kill_sock(TCP_connection->sock); + crypto_memzero(TCP_connection, sizeof(TCP_Client_Connection)); + free(TCP_connection); +} diff --git a/libs/libtox/src/toxcore/TCP_client.h b/libs/libtox/src/toxcore/TCP_client.h new file mode 100644 index 0000000000..212543147c --- /dev/null +++ b/libs/libtox/src/toxcore/TCP_client.h @@ -0,0 +1,167 @@ +/* + * Implementation of the TCP relay client part of Tox. + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2014 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifndef TCP_CLIENT_H +#define TCP_CLIENT_H + +#include "TCP_server.h" +#include "crypto_core.h" + +#define TCP_CONNECTION_TIMEOUT 10 + +typedef enum { + TCP_PROXY_NONE, + TCP_PROXY_HTTP, + TCP_PROXY_SOCKS5 +} TCP_PROXY_TYPE; + +typedef struct { + IP_Port ip_port; + uint8_t proxy_type; // a value from TCP_PROXY_TYPE +} TCP_Proxy_Info; + +enum { + TCP_CLIENT_NO_STATUS, + TCP_CLIENT_PROXY_HTTP_CONNECTING, + TCP_CLIENT_PROXY_SOCKS5_CONNECTING, + TCP_CLIENT_PROXY_SOCKS5_UNCONFIRMED, + TCP_CLIENT_CONNECTING, + TCP_CLIENT_UNCONFIRMED, + TCP_CLIENT_CONFIRMED, + TCP_CLIENT_DISCONNECTED, +}; +typedef struct { + uint8_t status; + Socket sock; + uint8_t self_public_key[CRYPTO_PUBLIC_KEY_SIZE]; /* our public key */ + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; /* public key of the server */ + IP_Port ip_port; /* The ip and port of the server */ + TCP_Proxy_Info proxy_info; + uint8_t recv_nonce[CRYPTO_NONCE_SIZE]; /* Nonce of received packets. */ + uint8_t sent_nonce[CRYPTO_NONCE_SIZE]; /* Nonce of sent packets. */ + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + uint16_t next_packet_length; + + uint8_t temp_secret_key[CRYPTO_SECRET_KEY_SIZE]; + + uint8_t last_packet[2 + MAX_PACKET_SIZE]; + uint16_t last_packet_length; + uint16_t last_packet_sent; + + TCP_Priority_List *priority_queue_start, *priority_queue_end; + + uint64_t kill_at; + + uint64_t last_pinged; + uint64_t ping_id; + + uint64_t ping_response_id; + uint64_t ping_request_id; + + struct { + uint8_t status; /* 0 if not used, 1 if other is offline, 2 if other is online. */ + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint32_t number; + } connections[NUM_CLIENT_CONNECTIONS]; + int (*response_callback)(void *object, uint8_t connection_id, const uint8_t *public_key); + void *response_callback_object; + int (*status_callback)(void *object, uint32_t number, uint8_t connection_id, uint8_t status); + void *status_callback_object; + int (*data_callback)(void *object, uint32_t number, uint8_t connection_id, const uint8_t *data, uint16_t length, + void *userdata); + void *data_callback_object; + int (*oob_data_callback)(void *object, const uint8_t *public_key, const uint8_t *data, uint16_t length, void *userdata); + void *oob_data_callback_object; + + int (*onion_callback)(void *object, const uint8_t *data, uint16_t length, void *userdata); + void *onion_callback_object; + + /* Can be used by user. */ + void *custom_object; + uint32_t custom_uint; +} TCP_Client_Connection; + +/* Create new TCP connection to ip_port/public_key + */ +TCP_Client_Connection *new_TCP_connection(IP_Port ip_port, const uint8_t *public_key, const uint8_t *self_public_key, + const uint8_t *self_secret_key, TCP_Proxy_Info *proxy_info); + +/* Run the TCP connection + */ +void do_TCP_connection(TCP_Client_Connection *TCP_connection, void *userdata); + +/* Kill the TCP connection + */ +void kill_TCP_connection(TCP_Client_Connection *TCP_connection); + +/* return 1 on success. + * return 0 if could not send packet. + * return -1 on failure (connection must be killed). + */ +int send_onion_request(TCP_Client_Connection *con, const uint8_t *data, uint16_t length); +void onion_response_handler(TCP_Client_Connection *con, int (*onion_callback)(void *object, const uint8_t *data, + uint16_t length, void *userdata), void *object); + +/* return 1 on success. + * return 0 if could not send packet. + * return -1 on failure (connection must be killed). + */ +int send_routing_request(TCP_Client_Connection *con, uint8_t *public_key); +void routing_response_handler(TCP_Client_Connection *con, int (*response_callback)(void *object, uint8_t connection_id, + const uint8_t *public_key), void *object); +void routing_status_handler(TCP_Client_Connection *con, int (*status_callback)(void *object, uint32_t number, + uint8_t connection_id, uint8_t status), void *object); + +/* return 1 on success. + * return 0 if could not send packet. + * return -1 on failure (connection must be killed). + */ +int send_disconnect_request(TCP_Client_Connection *con, uint8_t con_id); + +/* Set the number that will be used as an argument in the callbacks related to con_id. + * + * When not set by this function, the number is ~0. + * + * return 0 on success. + * return -1 on failure. + */ +int set_tcp_connection_number(TCP_Client_Connection *con, uint8_t con_id, uint32_t number); + +/* return 1 on success. + * return 0 if could not send packet. + * return -1 on failure. + */ +int send_data(TCP_Client_Connection *con, uint8_t con_id, const uint8_t *data, uint16_t length); +void routing_data_handler(TCP_Client_Connection *con, int (*data_callback)(void *object, uint32_t number, + uint8_t connection_id, const uint8_t *data, uint16_t length, void *userdata), void *object); + +/* return 1 on success. + * return 0 if could not send packet. + * return -1 on failure. + */ +int send_oob_packet(TCP_Client_Connection *con, const uint8_t *public_key, const uint8_t *data, uint16_t length); +void oob_data_handler(TCP_Client_Connection *con, int (*oob_data_callback)(void *object, const uint8_t *public_key, + const uint8_t *data, uint16_t length, void *userdata), void *object); + + +#endif diff --git a/libs/libtox/src/toxcore/TCP_connection.c b/libs/libtox/src/toxcore/TCP_connection.c new file mode 100644 index 0000000000..251594912a --- /dev/null +++ b/libs/libtox/src/toxcore/TCP_connection.c @@ -0,0 +1,1491 @@ +/* + * Handles TCP relay connections between two Tox clients. + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2015 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "TCP_connection.h" + +#include "util.h" + +#include <assert.h> + + +struct TCP_Connections { + DHT *dht; + + uint8_t self_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t self_secret_key[CRYPTO_SECRET_KEY_SIZE]; + + TCP_Connection_to *connections; + uint32_t connections_length; /* Length of connections array. */ + + TCP_con *tcp_connections; + uint32_t tcp_connections_length; /* Length of tcp_connections array. */ + + int (*tcp_data_callback)(void *object, int id, const uint8_t *data, uint16_t length, void *userdata); + void *tcp_data_callback_object; + + int (*tcp_oob_callback)(void *object, const uint8_t *public_key, unsigned int tcp_connections_number, + const uint8_t *data, uint16_t length, void *userdata); + void *tcp_oob_callback_object; + + int (*tcp_onion_callback)(void *object, const uint8_t *data, uint16_t length, void *userdata); + void *tcp_onion_callback_object; + + TCP_Proxy_Info proxy_info; + + bool onion_status; + uint16_t onion_num_conns; +}; + + +const uint8_t *tcp_connections_public_key(const TCP_Connections *tcp_c) +{ + return tcp_c->self_public_key; +} + + +/* Set the size of the array to num. + * + * return -1 if realloc fails. + * return 0 if it succeeds. + */ +#define realloc_tox_array(array, element_type, num, temp_pointer) \ + (num \ + ? (temp_pointer = (element_type *)realloc( \ + array, \ + (num) * sizeof(element_type)), \ + temp_pointer ? (array = temp_pointer, 0) : -1) \ + : (free(array), array = NULL, 0)) + + +/* return 1 if the connections_number is not valid. + * return 0 if the connections_number is valid. + */ +static bool connections_number_not_valid(const TCP_Connections *tcp_c, int connections_number) +{ + if ((unsigned int)connections_number >= tcp_c->connections_length) { + return 1; + } + + if (tcp_c->connections == NULL) { + return 1; + } + + if (tcp_c->connections[connections_number].status == TCP_CONN_NONE) { + return 1; + } + + return 0; +} + +/* return 1 if the tcp_connections_number is not valid. + * return 0 if the tcp_connections_number is valid. + */ +static bool tcp_connections_number_not_valid(const TCP_Connections *tcp_c, int tcp_connections_number) +{ + if ((unsigned int)tcp_connections_number >= tcp_c->tcp_connections_length) { + return 1; + } + + if (tcp_c->tcp_connections == NULL) { + return 1; + } + + if (tcp_c->tcp_connections[tcp_connections_number].status == TCP_CONN_NONE) { + return 1; + } + + return 0; +} + +/* Create a new empty connection. + * + * return -1 on failure. + * return connections_number on success. + */ +static int create_connection(TCP_Connections *tcp_c) +{ + uint32_t i; + + for (i = 0; i < tcp_c->connections_length; ++i) { + if (tcp_c->connections[i].status == TCP_CONN_NONE) { + return i; + } + } + + int id = -1; + + TCP_Connection_to *temp_pointer; + + if (realloc_tox_array(tcp_c->connections, TCP_Connection_to, tcp_c->connections_length + 1, + temp_pointer) == 0) { + id = tcp_c->connections_length; + ++tcp_c->connections_length; + memset(&(tcp_c->connections[id]), 0, sizeof(TCP_Connection_to)); + } + + return id; +} + +/* Create a new empty tcp connection. + * + * return -1 on failure. + * return tcp_connections_number on success. + */ +static int create_tcp_connection(TCP_Connections *tcp_c) +{ + uint32_t i; + + for (i = 0; i < tcp_c->tcp_connections_length; ++i) { + if (tcp_c->tcp_connections[i].status == TCP_CONN_NONE) { + return i; + } + } + + int id = -1; + + TCP_con *temp_pointer; + + if (realloc_tox_array(tcp_c->tcp_connections, TCP_con, tcp_c->tcp_connections_length + 1, temp_pointer) == 0) { + id = tcp_c->tcp_connections_length; + ++tcp_c->tcp_connections_length; + memset(&(tcp_c->tcp_connections[id]), 0, sizeof(TCP_con)); + } + + return id; +} + +/* Wipe a connection. + * + * return -1 on failure. + * return 0 on success. + */ +static int wipe_connection(TCP_Connections *tcp_c, int connections_number) +{ + if (connections_number_not_valid(tcp_c, connections_number)) { + return -1; + } + + uint32_t i; + memset(&(tcp_c->connections[connections_number]), 0 , sizeof(TCP_Connection_to)); + + for (i = tcp_c->connections_length; i != 0; --i) { + if (tcp_c->connections[i - 1].status != TCP_CONN_NONE) { + break; + } + } + + if (tcp_c->connections_length != i) { + tcp_c->connections_length = i; + TCP_Connection_to *temp_pointer; + realloc_tox_array(tcp_c->connections, TCP_Connection_to, tcp_c->connections_length, temp_pointer); + } + + return 0; +} + +/* Wipe a connection. + * + * return -1 on failure. + * return 0 on success. + */ +static int wipe_tcp_connection(TCP_Connections *tcp_c, int tcp_connections_number) +{ + if (tcp_connections_number_not_valid(tcp_c, tcp_connections_number)) { + return -1; + } + + uint32_t i; + memset(&(tcp_c->tcp_connections[tcp_connections_number]), 0 , sizeof(TCP_con)); + + for (i = tcp_c->tcp_connections_length; i != 0; --i) { + if (tcp_c->tcp_connections[i - 1].status != TCP_CONN_NONE) { + break; + } + } + + if (tcp_c->tcp_connections_length != i) { + tcp_c->tcp_connections_length = i; + TCP_con *temp_pointer; + realloc_tox_array(tcp_c->tcp_connections, TCP_con, tcp_c->tcp_connections_length, temp_pointer); + } + + return 0; +} + +static TCP_Connection_to *get_connection(const TCP_Connections *tcp_c, int connections_number) +{ + if (connections_number_not_valid(tcp_c, connections_number)) { + return 0; + } + + return &tcp_c->connections[connections_number]; +} + +static TCP_con *get_tcp_connection(const TCP_Connections *tcp_c, int tcp_connections_number) +{ + if (tcp_connections_number_not_valid(tcp_c, tcp_connections_number)) { + return 0; + } + + return &tcp_c->tcp_connections[tcp_connections_number]; +} + +/* Send a packet to the TCP connection. + * + * return -1 on failure. + * return 0 on success. + */ +int send_packet_tcp_connection(TCP_Connections *tcp_c, int connections_number, const uint8_t *packet, uint16_t length) +{ + TCP_Connection_to *con_to = get_connection(tcp_c, connections_number); + + if (!con_to) { + return -1; + } + + // TODO(irungentoo): detect and kill bad relays. + // TODO(irungentoo): thread safety? + unsigned int i; + int ret = -1; + + bool limit_reached = 0; + + for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) { + uint32_t tcp_con_num = con_to->connections[i].tcp_connection; + uint8_t status = con_to->connections[i].status; + uint8_t connection_id = con_to->connections[i].connection_id; + + if (tcp_con_num && status == TCP_CONNECTIONS_STATUS_ONLINE) { + tcp_con_num -= 1; + TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_con_num); + + if (!tcp_con) { + continue; + } + + ret = send_data(tcp_con->connection, connection_id, packet, length); + + if (ret == 0) { + limit_reached = 1; + } + + if (ret == 1) { + break; + } + } + } + + if (ret == 1) { + return 0; + } + + if (!limit_reached) { + ret = 0; + + /* Send oob packets to all relays tied to the connection. */ + for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) { + uint32_t tcp_con_num = con_to->connections[i].tcp_connection; + uint8_t status = con_to->connections[i].status; + + if (tcp_con_num && status == TCP_CONNECTIONS_STATUS_REGISTERED) { + tcp_con_num -= 1; + TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_con_num); + + if (!tcp_con) { + continue; + } + + if (send_oob_packet(tcp_con->connection, con_to->public_key, packet, length) == 1) { + ret += 1; + } + } + } + + if (ret >= 1) { + return 0; + } + + return -1; + } + + return -1; +} + +/* Return a random TCP connection number for use in send_tcp_onion_request. + * + * TODO(irungentoo): This number is just the index of an array that the elements + * can change without warning. + * + * return TCP connection number on success. + * return -1 on failure. + */ +int get_random_tcp_onion_conn_number(TCP_Connections *tcp_c) +{ + unsigned int i, r = rand(); + + for (i = 0; i < tcp_c->tcp_connections_length; ++i) { + unsigned int index = ((i + r) % tcp_c->tcp_connections_length); + + if (tcp_c->tcp_connections[index].onion && tcp_c->tcp_connections[index].status == TCP_CONN_CONNECTED) { + return index; + } + } + + return -1; +} + +/* Send an onion packet via the TCP relay corresponding to tcp_connections_number. + * + * return 0 on success. + * return -1 on failure. + */ +int tcp_send_onion_request(TCP_Connections *tcp_c, unsigned int tcp_connections_number, const uint8_t *data, + uint16_t length) +{ + if (tcp_connections_number >= tcp_c->tcp_connections_length) { + return -1; + } + + if (tcp_c->tcp_connections[tcp_connections_number].status == TCP_CONN_CONNECTED) { + int ret = send_onion_request(tcp_c->tcp_connections[tcp_connections_number].connection, data, length); + + if (ret == 1) { + return 0; + } + } + + return -1; +} + +/* Send an oob packet via the TCP relay corresponding to tcp_connections_number. + * + * return 0 on success. + * return -1 on failure. + */ +int tcp_send_oob_packet(TCP_Connections *tcp_c, unsigned int tcp_connections_number, const uint8_t *public_key, + const uint8_t *packet, uint16_t length) +{ + TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); + + if (!tcp_con) { + return -1; + } + + if (tcp_con->status != TCP_CONN_CONNECTED) { + return -1; + } + + int ret = send_oob_packet(tcp_con->connection, public_key, packet, length); + + if (ret == 1) { + return 0; + } + + return -1; +} + +/* Set the callback for TCP data packets. + */ +void set_packet_tcp_connection_callback(TCP_Connections *tcp_c, int (*tcp_data_callback)(void *object, int id, + const uint8_t *data, uint16_t length, void *userdata), void *object) +{ + tcp_c->tcp_data_callback = tcp_data_callback; + tcp_c->tcp_data_callback_object = object; +} + +/* Set the callback for TCP onion packets. + */ +void set_oob_packet_tcp_connection_callback(TCP_Connections *tcp_c, int (*tcp_oob_callback)(void *object, + const uint8_t *public_key, unsigned int tcp_connections_number, const uint8_t *data, uint16_t length, void *userdata), + void *object) +{ + tcp_c->tcp_oob_callback = tcp_oob_callback; + tcp_c->tcp_oob_callback_object = object; +} + +/* Set the callback for TCP oob data packets. + */ +void set_onion_packet_tcp_connection_callback(TCP_Connections *tcp_c, int (*tcp_onion_callback)(void *object, + const uint8_t *data, uint16_t length, void *userdata), void *object) +{ + tcp_c->tcp_onion_callback = tcp_onion_callback; + tcp_c->tcp_onion_callback_object = object; +} + + +/* Find the TCP connection with public_key. + * + * return connections_number on success. + * return -1 on failure. + */ +static int find_tcp_connection_to(TCP_Connections *tcp_c, const uint8_t *public_key) +{ + unsigned int i; + + for (i = 0; i < tcp_c->connections_length; ++i) { + TCP_Connection_to *con_to = get_connection(tcp_c, i); + + if (con_to) { + if (public_key_cmp(con_to->public_key, public_key) == 0) { + return i; + } + } + } + + return -1; +} + +/* Find the TCP connection to a relay with relay_pk. + * + * return connections_number on success. + * return -1 on failure. + */ +static int find_tcp_connection_relay(TCP_Connections *tcp_c, const uint8_t *relay_pk) +{ + unsigned int i; + + for (i = 0; i < tcp_c->tcp_connections_length; ++i) { + TCP_con *tcp_con = get_tcp_connection(tcp_c, i); + + if (tcp_con) { + if (tcp_con->status == TCP_CONN_SLEEPING) { + if (public_key_cmp(tcp_con->relay_pk, relay_pk) == 0) { + return i; + } + } else { + if (public_key_cmp(tcp_con->connection->public_key, relay_pk) == 0) { + return i; + } + } + } + } + + return -1; +} + +/* Create a new TCP connection to public_key. + * + * public_key must be the counterpart to the secret key that the other peer used with new_tcp_connections(). + * + * id is the id in the callbacks for that connection. + * + * return connections_number on success. + * return -1 on failure. + */ +int new_tcp_connection_to(TCP_Connections *tcp_c, const uint8_t *public_key, int id) +{ + if (find_tcp_connection_to(tcp_c, public_key) != -1) { + return -1; + } + + int connections_number = create_connection(tcp_c); + + if (connections_number == -1) { + return -1; + } + + TCP_Connection_to *con_to = &tcp_c->connections[connections_number]; + + con_to->status = TCP_CONN_VALID; + memcpy(con_to->public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); + con_to->id = id; + + return connections_number; +} + +/* return 0 on success. + * return -1 on failure. + */ +int kill_tcp_connection_to(TCP_Connections *tcp_c, int connections_number) +{ + TCP_Connection_to *con_to = get_connection(tcp_c, connections_number); + + if (!con_to) { + return -1; + } + + unsigned int i; + + for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) { + if (con_to->connections[i].tcp_connection) { + unsigned int tcp_connections_number = con_to->connections[i].tcp_connection - 1; + TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); + + if (!tcp_con) { + continue; + } + + if (tcp_con->status == TCP_CONN_CONNECTED) { + send_disconnect_request(tcp_con->connection, con_to->connections[i].connection_id); + } + + if (con_to->connections[i].status == TCP_CONNECTIONS_STATUS_ONLINE) { + --tcp_con->lock_count; + + if (con_to->status == TCP_CONN_SLEEPING) { + --tcp_con->sleep_count; + } + } + } + } + + return wipe_connection(tcp_c, connections_number); +} + +/* Set connection status. + * + * status of 1 means we are using the connection. + * status of 0 means we are not using it. + * + * Unused tcp connections will be disconnected from but kept in case they are needed. + * + * return 0 on success. + * return -1 on failure. + */ +int set_tcp_connection_to_status(TCP_Connections *tcp_c, int connections_number, bool status) +{ + TCP_Connection_to *con_to = get_connection(tcp_c, connections_number); + + if (!con_to) { + return -1; + } + + if (status) { + /* Connection is unsleeping. */ + if (con_to->status != TCP_CONN_SLEEPING) { + return -1; + } + + unsigned int i; + + for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) { + if (con_to->connections[i].tcp_connection) { + unsigned int tcp_connections_number = con_to->connections[i].tcp_connection - 1; + TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); + + if (!tcp_con) { + continue; + } + + if (tcp_con->status == TCP_CONN_SLEEPING) { + tcp_con->unsleep = 1; + } + } + } + + con_to->status = TCP_CONN_VALID; + return 0; + } + + /* Connection is going to sleep. */ + if (con_to->status != TCP_CONN_VALID) { + return -1; + } + + unsigned int i; + + for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) { + if (con_to->connections[i].tcp_connection) { + unsigned int tcp_connections_number = con_to->connections[i].tcp_connection - 1; + TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); + + if (!tcp_con) { + continue; + } + + if (con_to->connections[i].status == TCP_CONNECTIONS_STATUS_ONLINE) { + ++tcp_con->sleep_count; + } + } + } + + con_to->status = TCP_CONN_SLEEPING; + return 0; +} + +static bool tcp_connection_in_conn(TCP_Connection_to *con_to, unsigned int tcp_connections_number) +{ + unsigned int i; + + for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) { + if (con_to->connections[i].tcp_connection == (tcp_connections_number + 1)) { + return 1; + } + } + + return 0; +} + +/* return index on success. + * return -1 on failure. + */ +static int add_tcp_connection_to_conn(TCP_Connection_to *con_to, unsigned int tcp_connections_number) +{ + unsigned int i; + + if (tcp_connection_in_conn(con_to, tcp_connections_number)) { + return -1; + } + + for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) { + if (con_to->connections[i].tcp_connection == 0) { + con_to->connections[i].tcp_connection = tcp_connections_number + 1; + con_to->connections[i].status = TCP_CONNECTIONS_STATUS_NONE; + con_to->connections[i].connection_id = 0; + return i; + } + } + + return -1; +} + +/* return index on success. + * return -1 on failure. + */ +static int rm_tcp_connection_from_conn(TCP_Connection_to *con_to, unsigned int tcp_connections_number) +{ + unsigned int i; + + for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) { + if (con_to->connections[i].tcp_connection == (tcp_connections_number + 1)) { + con_to->connections[i].tcp_connection = 0; + con_to->connections[i].status = TCP_CONNECTIONS_STATUS_NONE; + con_to->connections[i].connection_id = 0; + return i; + } + } + + return -1; +} + +/* return number of online connections on success. + * return -1 on failure. + */ +static unsigned int online_tcp_connection_from_conn(TCP_Connection_to *con_to) +{ + unsigned int i, count = 0; + + for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) { + if (con_to->connections[i].tcp_connection) { + if (con_to->connections[i].status == TCP_CONNECTIONS_STATUS_ONLINE) { + ++count; + } + } + } + + return count; +} + +/* return index on success. + * return -1 on failure. + */ +static int set_tcp_connection_status(TCP_Connection_to *con_to, unsigned int tcp_connections_number, + unsigned int status, uint8_t connection_id) +{ + unsigned int i; + + for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) { + if (con_to->connections[i].tcp_connection == (tcp_connections_number + 1)) { + + if (con_to->connections[i].status == status) { + return -1; + } + + con_to->connections[i].status = status; + con_to->connections[i].connection_id = connection_id; + return i; + } + } + + return -1; +} + +/* Kill a TCP relay connection. + * + * return 0 on success. + * return -1 on failure. + */ +static int kill_tcp_relay_connection(TCP_Connections *tcp_c, int tcp_connections_number) +{ + TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); + + if (!tcp_con) { + return -1; + } + + unsigned int i; + + for (i = 0; i < tcp_c->connections_length; ++i) { + TCP_Connection_to *con_to = get_connection(tcp_c, i); + + if (con_to) { + rm_tcp_connection_from_conn(con_to, tcp_connections_number); + } + } + + if (tcp_con->onion) { + --tcp_c->onion_num_conns; + } + + kill_TCP_connection(tcp_con->connection); + + return wipe_tcp_connection(tcp_c, tcp_connections_number); +} + +static int reconnect_tcp_relay_connection(TCP_Connections *tcp_c, int tcp_connections_number) +{ + TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); + + if (!tcp_con) { + return -1; + } + + if (tcp_con->status == TCP_CONN_SLEEPING) { + return -1; + } + + IP_Port ip_port = tcp_con->connection->ip_port; + uint8_t relay_pk[CRYPTO_PUBLIC_KEY_SIZE]; + memcpy(relay_pk, tcp_con->connection->public_key, CRYPTO_PUBLIC_KEY_SIZE); + kill_TCP_connection(tcp_con->connection); + tcp_con->connection = new_TCP_connection(ip_port, relay_pk, tcp_c->self_public_key, tcp_c->self_secret_key, + &tcp_c->proxy_info); + + if (!tcp_con->connection) { + kill_tcp_relay_connection(tcp_c, tcp_connections_number); + return -1; + } + + unsigned int i; + + for (i = 0; i < tcp_c->connections_length; ++i) { + TCP_Connection_to *con_to = get_connection(tcp_c, i); + + if (con_to) { + set_tcp_connection_status(con_to, tcp_connections_number, TCP_CONNECTIONS_STATUS_NONE, 0); + } + } + + if (tcp_con->onion) { + --tcp_c->onion_num_conns; + tcp_con->onion = 0; + } + + tcp_con->lock_count = 0; + tcp_con->sleep_count = 0; + tcp_con->connected_time = 0; + tcp_con->status = TCP_CONN_VALID; + tcp_con->unsleep = 0; + + return 0; +} + +static int sleep_tcp_relay_connection(TCP_Connections *tcp_c, int tcp_connections_number) +{ + TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); + + if (!tcp_con) { + return -1; + } + + if (tcp_con->status != TCP_CONN_CONNECTED) { + return -1; + } + + if (tcp_con->lock_count != tcp_con->sleep_count) { + return -1; + } + + tcp_con->ip_port = tcp_con->connection->ip_port; + memcpy(tcp_con->relay_pk, tcp_con->connection->public_key, CRYPTO_PUBLIC_KEY_SIZE); + + kill_TCP_connection(tcp_con->connection); + tcp_con->connection = NULL; + + unsigned int i; + + for (i = 0; i < tcp_c->connections_length; ++i) { + TCP_Connection_to *con_to = get_connection(tcp_c, i); + + if (con_to) { + set_tcp_connection_status(con_to, tcp_connections_number, TCP_CONNECTIONS_STATUS_NONE, 0); + } + } + + if (tcp_con->onion) { + --tcp_c->onion_num_conns; + tcp_con->onion = 0; + } + + tcp_con->lock_count = 0; + tcp_con->sleep_count = 0; + tcp_con->connected_time = 0; + tcp_con->status = TCP_CONN_SLEEPING; + tcp_con->unsleep = 0; + + return 0; +} + +static int unsleep_tcp_relay_connection(TCP_Connections *tcp_c, int tcp_connections_number) +{ + TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); + + if (!tcp_con) { + return -1; + } + + if (tcp_con->status != TCP_CONN_SLEEPING) { + return -1; + } + + tcp_con->connection = new_TCP_connection(tcp_con->ip_port, tcp_con->relay_pk, tcp_c->self_public_key, + tcp_c->self_secret_key, &tcp_c->proxy_info); + + if (!tcp_con->connection) { + kill_tcp_relay_connection(tcp_c, tcp_connections_number); + return -1; + } + + tcp_con->lock_count = 0; + tcp_con->sleep_count = 0; + tcp_con->connected_time = 0; + tcp_con->status = TCP_CONN_VALID; + tcp_con->unsleep = 0; + return 0; +} + +/* Send a TCP routing request. + * + * return 0 on success. + * return -1 on failure. + */ +static int send_tcp_relay_routing_request(TCP_Connections *tcp_c, int tcp_connections_number, uint8_t *public_key) +{ + TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); + + if (!tcp_con) { + return -1; + } + + if (tcp_con->status == TCP_CONN_SLEEPING) { + return -1; + } + + if (send_routing_request(tcp_con->connection, public_key) != 1) { + return -1; + } + + return 0; +} + +static int tcp_response_callback(void *object, uint8_t connection_id, const uint8_t *public_key) +{ + TCP_Client_Connection *TCP_client_con = (TCP_Client_Connection *)object; + TCP_Connections *tcp_c = (TCP_Connections *)TCP_client_con->custom_object; + + unsigned int tcp_connections_number = TCP_client_con->custom_uint; + TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); + + if (!tcp_con) { + return -1; + } + + int connections_number = find_tcp_connection_to(tcp_c, public_key); + + if (connections_number == -1) { + return -1; + } + + TCP_Connection_to *con_to = get_connection(tcp_c, connections_number); + + if (con_to == NULL) { + return -1; + } + + if (set_tcp_connection_status(con_to, tcp_connections_number, TCP_CONNECTIONS_STATUS_REGISTERED, connection_id) == -1) { + return -1; + } + + set_tcp_connection_number(tcp_con->connection, connection_id, connections_number); + + return 0; +} + +static int tcp_status_callback(void *object, uint32_t number, uint8_t connection_id, uint8_t status) +{ + TCP_Client_Connection *TCP_client_con = (TCP_Client_Connection *)object; + TCP_Connections *tcp_c = (TCP_Connections *)TCP_client_con->custom_object; + + unsigned int tcp_connections_number = TCP_client_con->custom_uint; + TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); + TCP_Connection_to *con_to = get_connection(tcp_c, number); + + if (!con_to || !tcp_con) { + return -1; + } + + if (status == 1) { + if (set_tcp_connection_status(con_to, tcp_connections_number, TCP_CONNECTIONS_STATUS_REGISTERED, connection_id) == -1) { + return -1; + } + + --tcp_con->lock_count; + + if (con_to->status == TCP_CONN_SLEEPING) { + --tcp_con->sleep_count; + } + } else if (status == 2) { + if (set_tcp_connection_status(con_to, tcp_connections_number, TCP_CONNECTIONS_STATUS_ONLINE, connection_id) == -1) { + return -1; + } + + ++tcp_con->lock_count; + + if (con_to->status == TCP_CONN_SLEEPING) { + ++tcp_con->sleep_count; + } + } + + return 0; +} + +static int tcp_conn_data_callback(void *object, uint32_t number, uint8_t connection_id, const uint8_t *data, + uint16_t length, void *userdata) +{ + if (length == 0) { + return -1; + } + + TCP_Client_Connection *TCP_client_con = (TCP_Client_Connection *)object; + TCP_Connections *tcp_c = (TCP_Connections *)TCP_client_con->custom_object; + + unsigned int tcp_connections_number = TCP_client_con->custom_uint; + TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); + + if (!tcp_con) { + return -1; + } + + TCP_Connection_to *con_to = get_connection(tcp_c, number); + + if (!con_to) { + return -1; + } + + if (tcp_c->tcp_data_callback) { + tcp_c->tcp_data_callback(tcp_c->tcp_data_callback_object, con_to->id, data, length, userdata); + } + + return 0; +} + +static int tcp_conn_oob_callback(void *object, const uint8_t *public_key, const uint8_t *data, uint16_t length, + void *userdata) +{ + if (length == 0) { + return -1; + } + + TCP_Client_Connection *TCP_client_con = (TCP_Client_Connection *)object; + TCP_Connections *tcp_c = (TCP_Connections *)TCP_client_con->custom_object; + + unsigned int tcp_connections_number = TCP_client_con->custom_uint; + TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); + + if (!tcp_con) { + return -1; + } + + /* TODO(irungentoo): optimize */ + int connections_number = find_tcp_connection_to(tcp_c, public_key); + + TCP_Connection_to *con_to = get_connection(tcp_c, connections_number); + + if (con_to && tcp_connection_in_conn(con_to, tcp_connections_number)) { + return tcp_conn_data_callback(object, connections_number, 0, data, length, userdata); + } + + if (tcp_c->tcp_oob_callback) { + tcp_c->tcp_oob_callback(tcp_c->tcp_oob_callback_object, public_key, tcp_connections_number, data, length, userdata); + } + + return 0; +} + +static int tcp_onion_callback(void *object, const uint8_t *data, uint16_t length, void *userdata) +{ + TCP_Connections *tcp_c = (TCP_Connections *)object; + + if (tcp_c->tcp_onion_callback) { + tcp_c->tcp_onion_callback(tcp_c->tcp_onion_callback_object, data, length, userdata); + } + + return 0; +} + +/* Set callbacks for the TCP relay connection. + * + * return 0 on success. + * return -1 on failure. + */ +static int tcp_relay_set_callbacks(TCP_Connections *tcp_c, int tcp_connections_number) +{ + TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); + + if (!tcp_con) { + return -1; + } + + TCP_Client_Connection *con = tcp_con->connection; + + con->custom_object = tcp_c; + con->custom_uint = tcp_connections_number; + onion_response_handler(con, &tcp_onion_callback, tcp_c); + routing_response_handler(con, &tcp_response_callback, con); + routing_status_handler(con, &tcp_status_callback, con); + routing_data_handler(con, &tcp_conn_data_callback, con); + oob_data_handler(con, &tcp_conn_oob_callback, con); + + return 0; +} + +static int tcp_relay_on_online(TCP_Connections *tcp_c, int tcp_connections_number) +{ + TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); + + if (!tcp_con) { + return -1; + } + + unsigned int i, sent = 0; + + for (i = 0; i < tcp_c->connections_length; ++i) { + TCP_Connection_to *con_to = get_connection(tcp_c, i); + + if (con_to) { + if (tcp_connection_in_conn(con_to, tcp_connections_number)) { + if (send_tcp_relay_routing_request(tcp_c, tcp_connections_number, con_to->public_key) == 0) { + ++sent; + } + } + } + } + + tcp_relay_set_callbacks(tcp_c, tcp_connections_number); + tcp_con->status = TCP_CONN_CONNECTED; + + /* If this connection isn't used by any connection, we don't need to wait for them to come online. */ + if (sent) { + tcp_con->connected_time = unix_time(); + } else { + tcp_con->connected_time = 0; + } + + if (tcp_c->onion_status && tcp_c->onion_num_conns < NUM_ONION_TCP_CONNECTIONS) { + tcp_con->onion = 1; + ++tcp_c->onion_num_conns; + } + + return 0; +} + +static int add_tcp_relay_instance(TCP_Connections *tcp_c, IP_Port ip_port, const uint8_t *relay_pk) +{ + if (ip_port.ip.family == TCP_INET) { + ip_port.ip.family = TOX_AF_INET; + } else if (ip_port.ip.family == TCP_INET6) { + ip_port.ip.family = TOX_AF_INET6; + } + + if (ip_port.ip.family != TOX_AF_INET && ip_port.ip.family != TOX_AF_INET6) { + return -1; + } + + int tcp_connections_number = create_tcp_connection(tcp_c); + + if (tcp_connections_number == -1) { + return -1; + } + + TCP_con *tcp_con = &tcp_c->tcp_connections[tcp_connections_number]; + + tcp_con->connection = new_TCP_connection(ip_port, relay_pk, tcp_c->self_public_key, tcp_c->self_secret_key, + &tcp_c->proxy_info); + + if (!tcp_con->connection) { + return -1; + } + + tcp_con->status = TCP_CONN_VALID; + + return tcp_connections_number; +} + +/* Add a TCP relay to the TCP_Connections instance. + * + * return 0 on success. + * return -1 on failure. + */ +int add_tcp_relay_global(TCP_Connections *tcp_c, IP_Port ip_port, const uint8_t *relay_pk) +{ + int tcp_connections_number = find_tcp_connection_relay(tcp_c, relay_pk); + + if (tcp_connections_number != -1) { + return -1; + } + + if (add_tcp_relay_instance(tcp_c, ip_port, relay_pk) == -1) { + return -1; + } + + return 0; +} + +/* Add a TCP relay tied to a connection. + * + * return 0 on success. + * return -1 on failure. + */ +int add_tcp_number_relay_connection(TCP_Connections *tcp_c, int connections_number, unsigned int tcp_connections_number) +{ + TCP_Connection_to *con_to = get_connection(tcp_c, connections_number); + + if (!con_to) { + return -1; + } + + TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); + + if (!tcp_con) { + return -1; + } + + if (con_to->status != TCP_CONN_SLEEPING && tcp_con->status == TCP_CONN_SLEEPING) { + tcp_con->unsleep = 1; + } + + if (add_tcp_connection_to_conn(con_to, tcp_connections_number) == -1) { + return -1; + } + + if (tcp_con->status == TCP_CONN_CONNECTED) { + if (send_tcp_relay_routing_request(tcp_c, tcp_connections_number, con_to->public_key) == 0) { + tcp_con->connected_time = unix_time(); + } + } + + return 0; +} + +/* Add a TCP relay tied to a connection. + * + * This should be called with the same relay by two peers who want to create a TCP connection with each other. + * + * return 0 on success. + * return -1 on failure. + */ +int add_tcp_relay_connection(TCP_Connections *tcp_c, int connections_number, IP_Port ip_port, const uint8_t *relay_pk) +{ + TCP_Connection_to *con_to = get_connection(tcp_c, connections_number); + + if (!con_to) { + return -1; + } + + int tcp_connections_number = find_tcp_connection_relay(tcp_c, relay_pk); + + if (tcp_connections_number != -1) { + return add_tcp_number_relay_connection(tcp_c, connections_number, tcp_connections_number); + } + + if (online_tcp_connection_from_conn(con_to) >= RECOMMENDED_FRIEND_TCP_CONNECTIONS) { + return -1; + } + + tcp_connections_number = add_tcp_relay_instance(tcp_c, ip_port, relay_pk); + + TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); + + if (!tcp_con) { + return -1; + } + + if (add_tcp_connection_to_conn(con_to, tcp_connections_number) == -1) { + return -1; + } + + return 0; +} + +/* return number of online tcp relays tied to the connection on success. + * return 0 on failure. + */ +unsigned int tcp_connection_to_online_tcp_relays(TCP_Connections *tcp_c, int connections_number) +{ + TCP_Connection_to *con_to = get_connection(tcp_c, connections_number); + + if (!con_to) { + return 0; + } + + return online_tcp_connection_from_conn(con_to); +} + +/* Copy a maximum of max_num TCP relays we are connected to to tcp_relays. + * NOTE that the family of the copied ip ports will be set to TCP_INET or TCP_INET6. + * + * return number of relays copied to tcp_relays on success. + * return 0 on failure. + */ +unsigned int tcp_copy_connected_relays(TCP_Connections *tcp_c, Node_format *tcp_relays, uint16_t max_num) +{ + unsigned int i, copied = 0, r = rand(); + + for (i = 0; (i < tcp_c->tcp_connections_length) && (copied < max_num); ++i) { + TCP_con *tcp_con = get_tcp_connection(tcp_c, (i + r) % tcp_c->tcp_connections_length); + + if (!tcp_con) { + continue; + } + + if (tcp_con->status == TCP_CONN_CONNECTED) { + memcpy(tcp_relays[copied].public_key, tcp_con->connection->public_key, CRYPTO_PUBLIC_KEY_SIZE); + tcp_relays[copied].ip_port = tcp_con->connection->ip_port; + + if (tcp_relays[copied].ip_port.ip.family == TOX_AF_INET) { + tcp_relays[copied].ip_port.ip.family = TCP_INET; + } else if (tcp_relays[copied].ip_port.ip.family == TOX_AF_INET6) { + tcp_relays[copied].ip_port.ip.family = TCP_INET6; + } + + ++copied; + } + } + + return copied; +} + +/* Set if we want TCP_connection to allocate some connection for onion use. + * + * If status is 1, allocate some connections. if status is 0, don't. + * + * return 0 on success. + * return -1 on failure. + */ +int set_tcp_onion_status(TCP_Connections *tcp_c, bool status) +{ + if (tcp_c->onion_status == status) { + return -1; + } + + if (status) { + unsigned int i; + + for (i = 0; i < tcp_c->tcp_connections_length; ++i) { + TCP_con *tcp_con = get_tcp_connection(tcp_c, i); + + if (tcp_con) { + if (tcp_con->status == TCP_CONN_CONNECTED && !tcp_con->onion) { + ++tcp_c->onion_num_conns; + tcp_con->onion = 1; + } + } + + if (tcp_c->onion_num_conns >= NUM_ONION_TCP_CONNECTIONS) { + break; + } + } + + if (tcp_c->onion_num_conns < NUM_ONION_TCP_CONNECTIONS) { + unsigned int wakeup = NUM_ONION_TCP_CONNECTIONS - tcp_c->onion_num_conns; + + for (i = 0; i < tcp_c->tcp_connections_length; ++i) { + TCP_con *tcp_con = get_tcp_connection(tcp_c, i); + + if (tcp_con) { + if (tcp_con->status == TCP_CONN_SLEEPING) { + tcp_con->unsleep = 1; + } + } + + if (!wakeup) { + break; + } + } + } + + tcp_c->onion_status = 1; + } else { + unsigned int i; + + for (i = 0; i < tcp_c->tcp_connections_length; ++i) { + TCP_con *tcp_con = get_tcp_connection(tcp_c, i); + + if (tcp_con) { + if (tcp_con->onion) { + --tcp_c->onion_num_conns; + tcp_con->onion = 0; + } + } + } + + tcp_c->onion_status = 0; + } + + return 0; +} + +/* Returns a new TCP_Connections object associated with the secret_key. + * + * In order for others to connect to this instance new_tcp_connection_to() must be called with the + * public_key associated with secret_key. + * + * Returns NULL on failure. + */ +TCP_Connections *new_tcp_connections(const uint8_t *secret_key, TCP_Proxy_Info *proxy_info) +{ + if (secret_key == NULL) { + return NULL; + } + + TCP_Connections *temp = (TCP_Connections *)calloc(1, sizeof(TCP_Connections)); + + if (temp == NULL) { + return NULL; + } + + memcpy(temp->self_secret_key, secret_key, CRYPTO_SECRET_KEY_SIZE); + crypto_derive_public_key(temp->self_public_key, temp->self_secret_key); + temp->proxy_info = *proxy_info; + + return temp; +} + +static void do_tcp_conns(TCP_Connections *tcp_c, void *userdata) +{ + unsigned int i; + + for (i = 0; i < tcp_c->tcp_connections_length; ++i) { + TCP_con *tcp_con = get_tcp_connection(tcp_c, i); + + if (tcp_con) { + if (tcp_con->status != TCP_CONN_SLEEPING) { + do_TCP_connection(tcp_con->connection, userdata); + + /* callbacks can change TCP connection address. */ + tcp_con = get_tcp_connection(tcp_c, i); + + // Make sure the TCP connection wasn't dropped in any of the callbacks. + assert(tcp_con != NULL); + + if (tcp_con->connection->status == TCP_CLIENT_DISCONNECTED) { + if (tcp_con->status == TCP_CONN_CONNECTED) { + reconnect_tcp_relay_connection(tcp_c, i); + } else { + kill_tcp_relay_connection(tcp_c, i); + } + + continue; + } + + if (tcp_con->status == TCP_CONN_VALID && tcp_con->connection->status == TCP_CLIENT_CONFIRMED) { + tcp_relay_on_online(tcp_c, i); + } + + if (tcp_con->status == TCP_CONN_CONNECTED && !tcp_con->onion && tcp_con->lock_count + && tcp_con->lock_count == tcp_con->sleep_count + && is_timeout(tcp_con->connected_time, TCP_CONNECTION_ANNOUNCE_TIMEOUT)) { + sleep_tcp_relay_connection(tcp_c, i); + } + } + + if (tcp_con->status == TCP_CONN_SLEEPING && tcp_con->unsleep) { + unsleep_tcp_relay_connection(tcp_c, i); + } + } + } +} + +static void kill_nonused_tcp(TCP_Connections *tcp_c) +{ + if (tcp_c->tcp_connections_length == 0) { + return; + } + + unsigned int i; + unsigned int num_online = 0; + unsigned int num_kill = 0; + VLA(unsigned int, to_kill, tcp_c->tcp_connections_length); + + for (i = 0; i < tcp_c->tcp_connections_length; ++i) { + TCP_con *tcp_con = get_tcp_connection(tcp_c, i); + + if (tcp_con) { + if (tcp_con->status == TCP_CONN_CONNECTED) { + if (!tcp_con->onion && !tcp_con->lock_count && is_timeout(tcp_con->connected_time, TCP_CONNECTION_ANNOUNCE_TIMEOUT)) { + to_kill[num_kill] = i; + ++num_kill; + } + + ++num_online; + } + } + } + + if (num_online <= RECOMMENDED_FRIEND_TCP_CONNECTIONS) { + return; + } + + unsigned int n = num_online - RECOMMENDED_FRIEND_TCP_CONNECTIONS; + + if (n < num_kill) { + num_kill = n; + } + + for (i = 0; i < num_kill; ++i) { + kill_tcp_relay_connection(tcp_c, to_kill[i]); + } +} + +void do_tcp_connections(TCP_Connections *tcp_c, void *userdata) +{ + do_tcp_conns(tcp_c, userdata); + kill_nonused_tcp(tcp_c); +} + +void kill_tcp_connections(TCP_Connections *tcp_c) +{ + unsigned int i; + + for (i = 0; i < tcp_c->tcp_connections_length; ++i) { + kill_TCP_connection(tcp_c->tcp_connections[i].connection); + } + + free(tcp_c->tcp_connections); + free(tcp_c->connections); + free(tcp_c); +} diff --git a/libs/libtox/src/toxcore/TCP_connection.h b/libs/libtox/src/toxcore/TCP_connection.h new file mode 100644 index 0000000000..a45129a7e8 --- /dev/null +++ b/libs/libtox/src/toxcore/TCP_connection.h @@ -0,0 +1,223 @@ +/* + * Handles TCP relay connections between two Tox clients. + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2015 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifndef TCP_CONNECTION_H +#define TCP_CONNECTION_H + +#include "TCP_client.h" + +#define TCP_CONN_NONE 0 +#define TCP_CONN_VALID 1 + +/* NOTE: only used by TCP_con */ +#define TCP_CONN_CONNECTED 2 + +/* Connection is not connected but can be quickly reconnected in case it is needed. */ +#define TCP_CONN_SLEEPING 3 + +#define TCP_CONNECTIONS_STATUS_NONE 0 +#define TCP_CONNECTIONS_STATUS_REGISTERED 1 +#define TCP_CONNECTIONS_STATUS_ONLINE 2 + +#define MAX_FRIEND_TCP_CONNECTIONS 6 + +/* Time until connection to friend gets killed (if it doesn't get locked within that time) */ +#define TCP_CONNECTION_ANNOUNCE_TIMEOUT (TCP_CONNECTION_TIMEOUT) + +/* The amount of recommended connections for each friend + NOTE: Must be at most (MAX_FRIEND_TCP_CONNECTIONS / 2) */ +#define RECOMMENDED_FRIEND_TCP_CONNECTIONS (MAX_FRIEND_TCP_CONNECTIONS / 2) + +/* Number of TCP connections used for onion purposes. */ +#define NUM_ONION_TCP_CONNECTIONS RECOMMENDED_FRIEND_TCP_CONNECTIONS + +typedef struct { + uint8_t status; + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The dht public key of the peer */ + + struct { + uint32_t tcp_connection; + unsigned int status; + unsigned int connection_id; + } connections[MAX_FRIEND_TCP_CONNECTIONS]; + + int id; /* id used in callbacks. */ +} TCP_Connection_to; + +typedef struct { + uint8_t status; + TCP_Client_Connection *connection; + uint64_t connected_time; + uint32_t lock_count; + uint32_t sleep_count; + bool onion; + + /* Only used when connection is sleeping. */ + IP_Port ip_port; + uint8_t relay_pk[CRYPTO_PUBLIC_KEY_SIZE]; + bool unsleep; /* set to 1 to unsleep connection. */ +} TCP_con; + +typedef struct TCP_Connections TCP_Connections; + +const uint8_t *tcp_connections_public_key(const TCP_Connections *tcp_c); + +/* Send a packet to the TCP connection. + * + * return -1 on failure. + * return 0 on success. + */ +int send_packet_tcp_connection(TCP_Connections *tcp_c, int connections_number, const uint8_t *packet, uint16_t length); + +/* Return a random TCP connection number for use in send_tcp_onion_request. + * + * TODO(irungentoo): This number is just the index of an array that the elements + * can change without warning. + * + * return TCP connection number on success. + * return -1 on failure. + */ +int get_random_tcp_onion_conn_number(TCP_Connections *tcp_c); + +/* Send an onion packet via the TCP relay corresponding to tcp_connections_number. + * + * return 0 on success. + * return -1 on failure. + */ +int tcp_send_onion_request(TCP_Connections *tcp_c, unsigned int tcp_connections_number, const uint8_t *data, + uint16_t length); + +/* Set if we want TCP_connection to allocate some connection for onion use. + * + * If status is 1, allocate some connections. if status is 0, don't. + * + * return 0 on success. + * return -1 on failure. + */ +int set_tcp_onion_status(TCP_Connections *tcp_c, bool status); + +/* Send an oob packet via the TCP relay corresponding to tcp_connections_number. + * + * return 0 on success. + * return -1 on failure. + */ +int tcp_send_oob_packet(TCP_Connections *tcp_c, unsigned int tcp_connections_number, const uint8_t *public_key, + const uint8_t *packet, uint16_t length); + +/* Set the callback for TCP data packets. + */ +void set_packet_tcp_connection_callback(TCP_Connections *tcp_c, int (*tcp_data_callback)(void *object, int id, + const uint8_t *data, uint16_t length, void *userdata), void *object); + +/* Set the callback for TCP onion packets. + */ +void set_onion_packet_tcp_connection_callback(TCP_Connections *tcp_c, int (*tcp_onion_callback)(void *object, + const uint8_t *data, uint16_t length, void *userdata), void *object); + +/* Set the callback for TCP oob data packets. + */ +void set_oob_packet_tcp_connection_callback(TCP_Connections *tcp_c, int (*tcp_oob_callback)(void *object, + const uint8_t *public_key, unsigned int tcp_connections_number, const uint8_t *data, uint16_t length, void *userdata), + void *object); + +/* Create a new TCP connection to public_key. + * + * public_key must be the counterpart to the secret key that the other peer used with new_tcp_connections(). + * + * id is the id in the callbacks for that connection. + * + * return connections_number on success. + * return -1 on failure. + */ +int new_tcp_connection_to(TCP_Connections *tcp_c, const uint8_t *public_key, int id); + +/* return 0 on success. + * return -1 on failure. + */ +int kill_tcp_connection_to(TCP_Connections *tcp_c, int connections_number); + +/* Set connection status. + * + * status of 1 means we are using the connection. + * status of 0 means we are not using it. + * + * Unused tcp connections will be disconnected from but kept in case they are needed. + * + * return 0 on success. + * return -1 on failure. + */ +int set_tcp_connection_to_status(TCP_Connections *tcp_c, int connections_number, bool status); + +/* return number of online tcp relays tied to the connection on success. + * return 0 on failure. + */ +unsigned int tcp_connection_to_online_tcp_relays(TCP_Connections *tcp_c, int connections_number); + +/* Add a TCP relay tied to a connection. + * + * NOTE: This can only be used during the tcp_oob_callback. + * + * return 0 on success. + * return -1 on failure. + */ +int add_tcp_number_relay_connection(TCP_Connections *tcp_c, int connections_number, + unsigned int tcp_connections_number); + +/* Add a TCP relay tied to a connection. + * + * This should be called with the same relay by two peers who want to create a TCP connection with each other. + * + * return 0 on success. + * return -1 on failure. + */ +int add_tcp_relay_connection(TCP_Connections *tcp_c, int connections_number, IP_Port ip_port, const uint8_t *relay_pk); + +/* Add a TCP relay to the instance. + * + * return 0 on success. + * return -1 on failure. + */ +int add_tcp_relay_global(TCP_Connections *tcp_c, IP_Port ip_port, const uint8_t *relay_pk); + +/* Copy a maximum of max_num TCP relays we are connected to to tcp_relays. + * NOTE that the family of the copied ip ports will be set to TCP_INET or TCP_INET6. + * + * return number of relays copied to tcp_relays on success. + * return 0 on failure. + */ +unsigned int tcp_copy_connected_relays(TCP_Connections *tcp_c, Node_format *tcp_relays, uint16_t max_num); + +/* Returns a new TCP_Connections object associated with the secret_key. + * + * In order for others to connect to this instance new_tcp_connection_to() must be called with the + * public_key associated with secret_key. + * + * Returns NULL on failure. + */ +TCP_Connections *new_tcp_connections(const uint8_t *secret_key, TCP_Proxy_Info *proxy_info); + +void do_tcp_connections(TCP_Connections *tcp_c, void *userdata); +void kill_tcp_connections(TCP_Connections *tcp_c); + +#endif + diff --git a/libs/libtox/src/toxcore/TCP_server.c b/libs/libtox/src/toxcore/TCP_server.c new file mode 100644 index 0000000000..9b94667aa1 --- /dev/null +++ b/libs/libtox/src/toxcore/TCP_server.c @@ -0,0 +1,1417 @@ +/* + * Implementation of the TCP relay server part of Tox. + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2014 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "TCP_server.h" + +#include "util.h" + +#if !defined(_WIN32) && !defined(__WIN32__) && !defined (WIN32) +#include <sys/ioctl.h> +#endif + +struct TCP_Server { + Onion *onion; + +#ifdef TCP_SERVER_USE_EPOLL + int efd; + uint64_t last_run_pinged; +#endif + Socket *socks_listening; + unsigned int num_listening_socks; + + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t secret_key[CRYPTO_SECRET_KEY_SIZE]; + TCP_Secure_Connection incoming_connection_queue[MAX_INCOMING_CONNECTIONS]; + uint16_t incoming_connection_queue_index; + TCP_Secure_Connection unconfirmed_connection_queue[MAX_INCOMING_CONNECTIONS]; + uint16_t unconfirmed_connection_queue_index; + + TCP_Secure_Connection *accepted_connection_array; + uint32_t size_accepted_connections; + uint32_t num_accepted_connections; + + uint64_t counter; + + BS_LIST accepted_key_list; +}; + +const uint8_t *tcp_server_public_key(const TCP_Server *tcp_server) +{ + return tcp_server->public_key; +} + +size_t tcp_server_listen_count(const TCP_Server *tcp_server) +{ + return tcp_server->num_listening_socks; +} + +/* This is needed to compile on Android below API 21 + */ +#ifndef EPOLLRDHUP +#define EPOLLRDHUP 0x2000 +#endif + +/* Set the size of the connection list to numfriends. + * + * return -1 if realloc fails. + * return 0 if it succeeds. + */ +static int realloc_connection(TCP_Server *TCP_server, uint32_t num) +{ + if (num == 0) { + free(TCP_server->accepted_connection_array); + TCP_server->accepted_connection_array = NULL; + TCP_server->size_accepted_connections = 0; + return 0; + } + + if (num == TCP_server->size_accepted_connections) { + return 0; + } + + TCP_Secure_Connection *new_connections = (TCP_Secure_Connection *)realloc( + TCP_server->accepted_connection_array, + num * sizeof(TCP_Secure_Connection)); + + if (new_connections == NULL) { + return -1; + } + + if (num > TCP_server->size_accepted_connections) { + uint32_t old_size = TCP_server->size_accepted_connections; + uint32_t size_new_entries = (num - old_size) * sizeof(TCP_Secure_Connection); + memset(new_connections + old_size, 0, size_new_entries); + } + + TCP_server->accepted_connection_array = new_connections; + TCP_server->size_accepted_connections = num; + return 0; +} + +/* return index corresponding to connection with peer on success + * return -1 on failure. + */ +static int get_TCP_connection_index(const TCP_Server *TCP_server, const uint8_t *public_key) +{ + return bs_list_find(&TCP_server->accepted_key_list, public_key); +} + + +static int kill_accepted(TCP_Server *TCP_server, int index); + +/* Add accepted TCP connection to the list. + * + * return index on success + * return -1 on failure + */ +static int add_accepted(TCP_Server *TCP_server, const TCP_Secure_Connection *con) +{ + int index = get_TCP_connection_index(TCP_server, con->public_key); + + if (index != -1) { /* If an old connection to the same public key exists, kill it. */ + kill_accepted(TCP_server, index); + index = -1; + } + + if (TCP_server->size_accepted_connections == TCP_server->num_accepted_connections) { + if (realloc_connection(TCP_server, TCP_server->size_accepted_connections + 4) == -1) { + return -1; + } + + index = TCP_server->num_accepted_connections; + } else { + uint32_t i; + + for (i = TCP_server->size_accepted_connections; i != 0; --i) { + if (TCP_server->accepted_connection_array[i - 1].status == TCP_STATUS_NO_STATUS) { + index = i - 1; + break; + } + } + } + + if (index == -1) { + fprintf(stderr, "FAIL index is -1\n"); + return -1; + } + + if (!bs_list_add(&TCP_server->accepted_key_list, con->public_key, index)) { + return -1; + } + + memcpy(&TCP_server->accepted_connection_array[index], con, sizeof(TCP_Secure_Connection)); + TCP_server->accepted_connection_array[index].status = TCP_STATUS_CONFIRMED; + ++TCP_server->num_accepted_connections; + TCP_server->accepted_connection_array[index].identifier = ++TCP_server->counter; + TCP_server->accepted_connection_array[index].last_pinged = unix_time(); + TCP_server->accepted_connection_array[index].ping_id = 0; + + return index; +} + +/* Delete accepted connection from list. + * + * return 0 on success + * return -1 on failure + */ +static int del_accepted(TCP_Server *TCP_server, int index) +{ + if ((uint32_t)index >= TCP_server->size_accepted_connections) { + return -1; + } + + if (TCP_server->accepted_connection_array[index].status == TCP_STATUS_NO_STATUS) { + return -1; + } + + if (!bs_list_remove(&TCP_server->accepted_key_list, TCP_server->accepted_connection_array[index].public_key, index)) { + return -1; + } + + crypto_memzero(&TCP_server->accepted_connection_array[index], sizeof(TCP_Secure_Connection)); + --TCP_server->num_accepted_connections; + + if (TCP_server->num_accepted_connections == 0) { + realloc_connection(TCP_server, 0); + } + + return 0; +} + +/* return the amount of data in the tcp recv buffer. + * return 0 on failure. + */ +unsigned int TCP_socket_data_recv_buffer(Socket sock) +{ +#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) + unsigned long count = 0; + ioctlsocket(sock, FIONREAD, &count); +#else + int count = 0; + ioctl(sock, FIONREAD, &count); +#endif + + return count; +} + +/* Read the next two bytes in TCP stream then convert them to + * length (host byte order). + * + * return length on success + * return 0 if nothing has been read from socket. + * return ~0 on failure. + */ +uint16_t read_TCP_length(Socket sock) +{ + unsigned int count = TCP_socket_data_recv_buffer(sock); + + if (count >= sizeof(uint16_t)) { + uint16_t length; + int len = recv(sock, (char *)&length, sizeof(uint16_t), MSG_NOSIGNAL); + + if (len != sizeof(uint16_t)) { + fprintf(stderr, "FAIL recv packet\n"); + return 0; + } + + length = net_ntohs(length); + + if (length > MAX_PACKET_SIZE) { + return ~0; + } + + return length; + } + + return 0; +} + +/* Read length bytes from socket. + * + * return length on success + * return -1 on failure/no data in buffer. + */ +int read_TCP_packet(Socket sock, uint8_t *data, uint16_t length) +{ + unsigned int count = TCP_socket_data_recv_buffer(sock); + + if (count >= length) { + int len = recv(sock, (char *)data, length, MSG_NOSIGNAL); + + if (len != length) { + fprintf(stderr, "FAIL recv packet\n"); + return -1; + } + + return len; + } + + return -1; +} + +/* return length of received packet on success. + * return 0 if could not read any packet. + * return -1 on failure (connection must be killed). + */ +int read_packet_TCP_secure_connection(Socket sock, uint16_t *next_packet_length, const uint8_t *shared_key, + uint8_t *recv_nonce, uint8_t *data, uint16_t max_len) +{ + if (*next_packet_length == 0) { + uint16_t len = read_TCP_length(sock); + + if (len == (uint16_t)~0) { + return -1; + } + + if (len == 0) { + return 0; + } + + *next_packet_length = len; + } + + if (max_len + CRYPTO_MAC_SIZE < *next_packet_length) { + return -1; + } + + VLA(uint8_t, data_encrypted, *next_packet_length); + int len_packet = read_TCP_packet(sock, data_encrypted, *next_packet_length); + + if (len_packet != *next_packet_length) { + return 0; + } + + *next_packet_length = 0; + + int len = decrypt_data_symmetric(shared_key, recv_nonce, data_encrypted, len_packet, data); + + if (len + CRYPTO_MAC_SIZE != len_packet) { + return -1; + } + + increment_nonce(recv_nonce); + + return len; +} + +/* return 0 if pending data was sent completely + * return -1 if it wasn't + */ +static int send_pending_data_nonpriority(TCP_Secure_Connection *con) +{ + if (con->last_packet_length == 0) { + return 0; + } + + uint16_t left = con->last_packet_length - con->last_packet_sent; + int len = send(con->sock, (const char *)(con->last_packet + con->last_packet_sent), left, MSG_NOSIGNAL); + + if (len <= 0) { + return -1; + } + + if (len == left) { + con->last_packet_length = 0; + con->last_packet_sent = 0; + return 0; + } + + con->last_packet_sent += len; + return -1; +} + +/* return 0 if pending data was sent completely + * return -1 if it wasn't + */ +static int send_pending_data(TCP_Secure_Connection *con) +{ + /* finish sending current non-priority packet */ + if (send_pending_data_nonpriority(con) == -1) { + return -1; + } + + TCP_Priority_List *p = con->priority_queue_start; + + while (p) { + uint16_t left = p->size - p->sent; + int len = send(con->sock, (const char *)(p->data + p->sent), left, MSG_NOSIGNAL); + + if (len != left) { + if (len > 0) { + p->sent += len; + } + + break; + } + + TCP_Priority_List *pp = p; + p = p->next; + free(pp); + } + + con->priority_queue_start = p; + + if (!p) { + con->priority_queue_end = NULL; + return 0; + } + + return -1; +} + +/* return 0 on failure (only if malloc fails) + * return 1 on success + */ +static bool add_priority(TCP_Secure_Connection *con, const uint8_t *packet, uint16_t size, uint16_t sent) +{ + TCP_Priority_List *p = con->priority_queue_end; + TCP_Priority_List *new_list = (TCP_Priority_List *)malloc(sizeof(TCP_Priority_List) + size); + + if (!new_list) { + return 0; + } + + new_list->next = NULL; + new_list->size = size; + new_list->sent = sent; + memcpy(new_list->data, packet, size); + + if (p) { + p->next = new_list; + } else { + con->priority_queue_start = new_list; + } + + con->priority_queue_end = new_list; + return 1; +} + +/* return 1 on success. + * return 0 if could not send packet. + * return -1 on failure (connection must be killed). + */ +static int write_packet_TCP_secure_connection(TCP_Secure_Connection *con, const uint8_t *data, uint16_t length, + bool priority) +{ + if (length + CRYPTO_MAC_SIZE > MAX_PACKET_SIZE) { + return -1; + } + + bool sendpriority = 1; + + if (send_pending_data(con) == -1) { + if (priority) { + sendpriority = 0; + } else { + return 0; + } + } + + VLA(uint8_t, packet, sizeof(uint16_t) + length + CRYPTO_MAC_SIZE); + + uint16_t c_length = net_htons(length + CRYPTO_MAC_SIZE); + memcpy(packet, &c_length, sizeof(uint16_t)); + int len = encrypt_data_symmetric(con->shared_key, con->sent_nonce, data, length, packet + sizeof(uint16_t)); + + if ((unsigned int)len != (SIZEOF_VLA(packet) - sizeof(uint16_t))) { + return -1; + } + + if (priority) { + len = sendpriority ? send(con->sock, (const char *)packet, SIZEOF_VLA(packet), MSG_NOSIGNAL) : 0; + + if (len <= 0) { + len = 0; + } + + increment_nonce(con->sent_nonce); + + if ((unsigned int)len == SIZEOF_VLA(packet)) { + return 1; + } + + return add_priority(con, packet, SIZEOF_VLA(packet), len); + } + + len = send(con->sock, (const char *)packet, SIZEOF_VLA(packet), MSG_NOSIGNAL); + + if (len <= 0) { + return 0; + } + + increment_nonce(con->sent_nonce); + + if ((unsigned int)len == SIZEOF_VLA(packet)) { + return 1; + } + + memcpy(con->last_packet, packet, SIZEOF_VLA(packet)); + con->last_packet_length = SIZEOF_VLA(packet); + con->last_packet_sent = len; + return 1; +} + +/* Kill a TCP_Secure_Connection + */ +static void kill_TCP_secure_connection(TCP_Secure_Connection *con) +{ + kill_sock(con->sock); + crypto_memzero(con, sizeof(TCP_Secure_Connection)); +} + +static int rm_connection_index(TCP_Server *TCP_server, TCP_Secure_Connection *con, uint8_t con_number); + +/* Kill an accepted TCP_Secure_Connection + * + * return -1 on failure. + * return 0 on success. + */ +static int kill_accepted(TCP_Server *TCP_server, int index) +{ + if ((uint32_t)index >= TCP_server->size_accepted_connections) { + return -1; + } + + uint32_t i; + + for (i = 0; i < NUM_CLIENT_CONNECTIONS; ++i) { + rm_connection_index(TCP_server, &TCP_server->accepted_connection_array[index], i); + } + + Socket sock = TCP_server->accepted_connection_array[index].sock; + + if (del_accepted(TCP_server, index) != 0) { + return -1; + } + + kill_sock(sock); + return 0; +} + +/* return 1 if everything went well. + * return -1 if the connection must be killed. + */ +static int handle_TCP_handshake(TCP_Secure_Connection *con, const uint8_t *data, uint16_t length, + const uint8_t *self_secret_key) +{ + if (length != TCP_CLIENT_HANDSHAKE_SIZE) { + return -1; + } + + if (con->status != TCP_STATUS_CONNECTED) { + return -1; + } + + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + encrypt_precompute(data, self_secret_key, shared_key); + uint8_t plain[TCP_HANDSHAKE_PLAIN_SIZE]; + int len = decrypt_data_symmetric(shared_key, data + CRYPTO_PUBLIC_KEY_SIZE, + data + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE, TCP_HANDSHAKE_PLAIN_SIZE + CRYPTO_MAC_SIZE, plain); + + if (len != TCP_HANDSHAKE_PLAIN_SIZE) { + return -1; + } + + memcpy(con->public_key, data, CRYPTO_PUBLIC_KEY_SIZE); + uint8_t temp_secret_key[CRYPTO_SECRET_KEY_SIZE]; + uint8_t resp_plain[TCP_HANDSHAKE_PLAIN_SIZE]; + crypto_new_keypair(resp_plain, temp_secret_key); + random_nonce(con->sent_nonce); + memcpy(resp_plain + CRYPTO_PUBLIC_KEY_SIZE, con->sent_nonce, CRYPTO_NONCE_SIZE); + memcpy(con->recv_nonce, plain + CRYPTO_PUBLIC_KEY_SIZE, CRYPTO_NONCE_SIZE); + + uint8_t response[TCP_SERVER_HANDSHAKE_SIZE]; + random_nonce(response); + + len = encrypt_data_symmetric(shared_key, response, resp_plain, TCP_HANDSHAKE_PLAIN_SIZE, + response + CRYPTO_NONCE_SIZE); + + if (len != TCP_HANDSHAKE_PLAIN_SIZE + CRYPTO_MAC_SIZE) { + return -1; + } + + if (TCP_SERVER_HANDSHAKE_SIZE != send(con->sock, (const char *)response, TCP_SERVER_HANDSHAKE_SIZE, MSG_NOSIGNAL)) { + return -1; + } + + encrypt_precompute(plain, temp_secret_key, con->shared_key); + con->status = TCP_STATUS_UNCONFIRMED; + return 1; +} + +/* return 1 if connection handshake was handled correctly. + * return 0 if we didn't get it yet. + * return -1 if the connection must be killed. + */ +static int read_connection_handshake(TCP_Secure_Connection *con, const uint8_t *self_secret_key) +{ + uint8_t data[TCP_CLIENT_HANDSHAKE_SIZE]; + int len = 0; + + if ((len = read_TCP_packet(con->sock, data, TCP_CLIENT_HANDSHAKE_SIZE)) != -1) { + return handle_TCP_handshake(con, data, len, self_secret_key); + } + + return 0; +} + +/* return 1 on success. + * return 0 if could not send packet. + * return -1 on failure (connection must be killed). + */ +static int send_routing_response(TCP_Secure_Connection *con, uint8_t rpid, const uint8_t *public_key) +{ + uint8_t data[1 + 1 + CRYPTO_PUBLIC_KEY_SIZE]; + data[0] = TCP_PACKET_ROUTING_RESPONSE; + data[1] = rpid; + memcpy(data + 2, public_key, CRYPTO_PUBLIC_KEY_SIZE); + + return write_packet_TCP_secure_connection(con, data, sizeof(data), 1); +} + +/* return 1 on success. + * return 0 if could not send packet. + * return -1 on failure (connection must be killed). + */ +static int send_connect_notification(TCP_Secure_Connection *con, uint8_t id) +{ + uint8_t data[2] = {TCP_PACKET_CONNECTION_NOTIFICATION, (uint8_t)(id + NUM_RESERVED_PORTS)}; + return write_packet_TCP_secure_connection(con, data, sizeof(data), 1); +} + +/* return 1 on success. + * return 0 if could not send packet. + * return -1 on failure (connection must be killed). + */ +static int send_disconnect_notification(TCP_Secure_Connection *con, uint8_t id) +{ + uint8_t data[2] = {TCP_PACKET_DISCONNECT_NOTIFICATION, (uint8_t)(id + NUM_RESERVED_PORTS)}; + return write_packet_TCP_secure_connection(con, data, sizeof(data), 1); +} + +/* return 0 on success. + * return -1 on failure (connection must be killed). + */ +static int handle_TCP_routing_req(TCP_Server *TCP_server, uint32_t con_id, const uint8_t *public_key) +{ + uint32_t i; + uint32_t index = ~0; + TCP_Secure_Connection *con = &TCP_server->accepted_connection_array[con_id]; + + /* If person tries to cennect to himself we deny the request*/ + if (public_key_cmp(con->public_key, public_key) == 0) { + if (send_routing_response(con, 0, public_key) == -1) { + return -1; + } + + return 0; + } + + for (i = 0; i < NUM_CLIENT_CONNECTIONS; ++i) { + if (con->connections[i].status != 0) { + if (public_key_cmp(public_key, con->connections[i].public_key) == 0) { + if (send_routing_response(con, i + NUM_RESERVED_PORTS, public_key) == -1) { + return -1; + } + + return 0; + } + } else if (index == (uint32_t)~0) { + index = i; + } + } + + if (index == (uint32_t)~0) { + if (send_routing_response(con, 0, public_key) == -1) { + return -1; + } + + return 0; + } + + int ret = send_routing_response(con, index + NUM_RESERVED_PORTS, public_key); + + if (ret == 0) { + return 0; + } + + if (ret == -1) { + return -1; + } + + con->connections[index].status = 1; + memcpy(con->connections[index].public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); + int other_index = get_TCP_connection_index(TCP_server, public_key); + + if (other_index != -1) { + uint32_t other_id = ~0; + TCP_Secure_Connection *other_conn = &TCP_server->accepted_connection_array[other_index]; + + for (i = 0; i < NUM_CLIENT_CONNECTIONS; ++i) { + if (other_conn->connections[i].status == 1 + && public_key_cmp(other_conn->connections[i].public_key, con->public_key) == 0) { + other_id = i; + break; + } + } + + if (other_id != (uint32_t)~0) { + con->connections[index].status = 2; + con->connections[index].index = other_index; + con->connections[index].other_id = other_id; + other_conn->connections[other_id].status = 2; + other_conn->connections[other_id].index = con_id; + other_conn->connections[other_id].other_id = index; + // TODO(irungentoo): return values? + send_connect_notification(con, index); + send_connect_notification(other_conn, other_id); + } + } + + return 0; +} + +/* return 0 on success. + * return -1 on failure (connection must be killed). + */ +static int handle_TCP_oob_send(TCP_Server *TCP_server, uint32_t con_id, const uint8_t *public_key, const uint8_t *data, + uint16_t length) +{ + if (length == 0 || length > TCP_MAX_OOB_DATA_LENGTH) { + return -1; + } + + TCP_Secure_Connection *con = &TCP_server->accepted_connection_array[con_id]; + + int other_index = get_TCP_connection_index(TCP_server, public_key); + + if (other_index != -1) { + VLA(uint8_t, resp_packet, 1 + CRYPTO_PUBLIC_KEY_SIZE + length); + resp_packet[0] = TCP_PACKET_OOB_RECV; + memcpy(resp_packet + 1, con->public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(resp_packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, data, length); + write_packet_TCP_secure_connection(&TCP_server->accepted_connection_array[other_index], resp_packet, + SIZEOF_VLA(resp_packet), 0); + } + + return 0; +} + +/* Remove connection with con_number from the connections array of con. + * + * return -1 on failure. + * return 0 on success. + */ +static int rm_connection_index(TCP_Server *TCP_server, TCP_Secure_Connection *con, uint8_t con_number) +{ + if (con_number >= NUM_CLIENT_CONNECTIONS) { + return -1; + } + + if (con->connections[con_number].status) { + uint32_t index = con->connections[con_number].index; + uint8_t other_id = con->connections[con_number].other_id; + + if (con->connections[con_number].status == 2) { + + if (index >= TCP_server->size_accepted_connections) { + return -1; + } + + TCP_server->accepted_connection_array[index].connections[other_id].other_id = 0; + TCP_server->accepted_connection_array[index].connections[other_id].index = 0; + TCP_server->accepted_connection_array[index].connections[other_id].status = 1; + // TODO(irungentoo): return values? + send_disconnect_notification(&TCP_server->accepted_connection_array[index], other_id); + } + + con->connections[con_number].index = 0; + con->connections[con_number].other_id = 0; + con->connections[con_number].status = 0; + return 0; + } + + return -1; +} + +static int handle_onion_recv_1(void *object, IP_Port dest, const uint8_t *data, uint16_t length) +{ + TCP_Server *TCP_server = (TCP_Server *)object; + uint32_t index = dest.ip.ip6.uint32[0]; + + if (index >= TCP_server->size_accepted_connections) { + return 1; + } + + TCP_Secure_Connection *con = &TCP_server->accepted_connection_array[index]; + + if (con->identifier != dest.ip.ip6.uint64[1]) { + return 1; + } + + VLA(uint8_t, packet, 1 + length); + memcpy(packet + 1, data, length); + packet[0] = TCP_PACKET_ONION_RESPONSE; + + if (write_packet_TCP_secure_connection(con, packet, SIZEOF_VLA(packet), 0) != 1) { + return 1; + } + + return 0; +} + +/* return 0 on success + * return -1 on failure + */ +static int handle_TCP_packet(TCP_Server *TCP_server, uint32_t con_id, const uint8_t *data, uint16_t length) +{ + if (length == 0) { + return -1; + } + + TCP_Secure_Connection *con = &TCP_server->accepted_connection_array[con_id]; + + switch (data[0]) { + case TCP_PACKET_ROUTING_REQUEST: { + if (length != 1 + CRYPTO_PUBLIC_KEY_SIZE) { + return -1; + } + + return handle_TCP_routing_req(TCP_server, con_id, data + 1); + } + + case TCP_PACKET_CONNECTION_NOTIFICATION: { + if (length != 2) { + return -1; + } + + break; + } + + case TCP_PACKET_DISCONNECT_NOTIFICATION: { + if (length != 2) { + return -1; + } + + return rm_connection_index(TCP_server, con, data[1] - NUM_RESERVED_PORTS); + } + + case TCP_PACKET_PING: { + if (length != 1 + sizeof(uint64_t)) { + return -1; + } + + uint8_t response[1 + sizeof(uint64_t)]; + response[0] = TCP_PACKET_PONG; + memcpy(response + 1, data + 1, sizeof(uint64_t)); + write_packet_TCP_secure_connection(con, response, sizeof(response), 1); + return 0; + } + + case TCP_PACKET_PONG: { + if (length != 1 + sizeof(uint64_t)) { + return -1; + } + + uint64_t ping_id; + memcpy(&ping_id, data + 1, sizeof(uint64_t)); + + if (ping_id) { + if (ping_id == con->ping_id) { + con->ping_id = 0; + } + + return 0; + } + + return -1; + } + + case TCP_PACKET_OOB_SEND: { + if (length <= 1 + CRYPTO_PUBLIC_KEY_SIZE) { + return -1; + } + + return handle_TCP_oob_send(TCP_server, con_id, data + 1, data + 1 + CRYPTO_PUBLIC_KEY_SIZE, + length - (1 + CRYPTO_PUBLIC_KEY_SIZE)); + } + + case TCP_PACKET_ONION_REQUEST: { + if (TCP_server->onion) { + if (length <= 1 + CRYPTO_NONCE_SIZE + ONION_SEND_BASE * 2) { + return -1; + } + + IP_Port source; + source.port = 0; // dummy initialise + source.ip.family = TCP_ONION_FAMILY; + source.ip.ip6.uint32[0] = con_id; + source.ip.ip6.uint32[1] = 0; + source.ip.ip6.uint64[1] = con->identifier; + onion_send_1(TCP_server->onion, data + 1 + CRYPTO_NONCE_SIZE, length - (1 + CRYPTO_NONCE_SIZE), source, + data + 1); + } + + return 0; + } + + case TCP_PACKET_ONION_RESPONSE: { + return -1; + } + + default: { + if (data[0] < NUM_RESERVED_PORTS) { + return -1; + } + + uint8_t c_id = data[0] - NUM_RESERVED_PORTS; + + if (c_id >= NUM_CLIENT_CONNECTIONS) { + return -1; + } + + if (con->connections[c_id].status == 0) { + return -1; + } + + if (con->connections[c_id].status != 2) { + return 0; + } + + uint32_t index = con->connections[c_id].index; + uint8_t other_c_id = con->connections[c_id].other_id + NUM_RESERVED_PORTS; + VLA(uint8_t, new_data, length); + memcpy(new_data, data, length); + new_data[0] = other_c_id; + int ret = write_packet_TCP_secure_connection(&TCP_server->accepted_connection_array[index], new_data, length, 0); + + if (ret == -1) { + return -1; + } + + return 0; + } + } + + return 0; +} + + +static int confirm_TCP_connection(TCP_Server *TCP_server, TCP_Secure_Connection *con, const uint8_t *data, + uint16_t length) +{ + int index = add_accepted(TCP_server, con); + + if (index == -1) { + kill_TCP_secure_connection(con); + return -1; + } + + crypto_memzero(con, sizeof(TCP_Secure_Connection)); + + if (handle_TCP_packet(TCP_server, index, data, length) == -1) { + kill_accepted(TCP_server, index); + return -1; + } + + return index; +} + +/* return index on success + * return -1 on failure + */ +static int accept_connection(TCP_Server *TCP_server, Socket sock) +{ + if (!sock_valid(sock)) { + return -1; + } + + if (!set_socket_nonblock(sock)) { + kill_sock(sock); + return -1; + } + + if (!set_socket_nosigpipe(sock)) { + kill_sock(sock); + return -1; + } + + uint16_t index = TCP_server->incoming_connection_queue_index % MAX_INCOMING_CONNECTIONS; + + TCP_Secure_Connection *conn = &TCP_server->incoming_connection_queue[index]; + + if (conn->status != TCP_STATUS_NO_STATUS) { + kill_TCP_secure_connection(conn); + } + + conn->status = TCP_STATUS_CONNECTED; + conn->sock = sock; + conn->next_packet_length = 0; + + ++TCP_server->incoming_connection_queue_index; + return index; +} + +static Socket new_listening_TCP_socket(int family, uint16_t port) +{ + Socket sock = net_socket(family, TOX_SOCK_STREAM, TOX_PROTO_TCP); + + if (!sock_valid(sock)) { + return ~0; + } + + int ok = set_socket_nonblock(sock); + + if (ok && family == TOX_AF_INET6) { + ok = set_socket_dualstack(sock); + } + + if (ok) { + ok = set_socket_reuseaddr(sock); + } + + ok = ok && bind_to_port(sock, family, port) && (listen(sock, TCP_MAX_BACKLOG) == 0); + + if (!ok) { + kill_sock(sock); + return ~0; + } + + return sock; +} + +TCP_Server *new_TCP_server(uint8_t ipv6_enabled, uint16_t num_sockets, const uint16_t *ports, const uint8_t *secret_key, + Onion *onion) +{ + if (num_sockets == 0 || ports == NULL) { + return NULL; + } + + if (networking_at_startup() != 0) { + return NULL; + } + + TCP_Server *temp = (TCP_Server *)calloc(1, sizeof(TCP_Server)); + + if (temp == NULL) { + return NULL; + } + + temp->socks_listening = (Socket *)calloc(num_sockets, sizeof(Socket)); + + if (temp->socks_listening == NULL) { + free(temp); + return NULL; + } + +#ifdef TCP_SERVER_USE_EPOLL + temp->efd = epoll_create(8); + + if (temp->efd == -1) { + free(temp->socks_listening); + free(temp); + return NULL; + } + +#endif + + uint8_t family; + + if (ipv6_enabled) { + family = TOX_AF_INET6; + } else { + family = TOX_AF_INET; + } + + uint32_t i; +#ifdef TCP_SERVER_USE_EPOLL + struct epoll_event ev; +#endif + + for (i = 0; i < num_sockets; ++i) { + Socket sock = new_listening_TCP_socket(family, ports[i]); + + if (sock_valid(sock)) { +#ifdef TCP_SERVER_USE_EPOLL + ev.events = EPOLLIN | EPOLLET; + ev.data.u64 = sock | ((uint64_t)TCP_SOCKET_LISTENING << 32); + + if (epoll_ctl(temp->efd, EPOLL_CTL_ADD, sock, &ev) == -1) { + continue; + } + +#endif + + temp->socks_listening[temp->num_listening_socks] = sock; + ++temp->num_listening_socks; + } + } + + if (temp->num_listening_socks == 0) { + free(temp->socks_listening); + free(temp); + return NULL; + } + + if (onion) { + temp->onion = onion; + set_callback_handle_recv_1(onion, &handle_onion_recv_1, temp); + } + + memcpy(temp->secret_key, secret_key, CRYPTO_SECRET_KEY_SIZE); + crypto_derive_public_key(temp->public_key, temp->secret_key); + + bs_list_init(&temp->accepted_key_list, CRYPTO_PUBLIC_KEY_SIZE, 8); + + return temp; +} + +static void do_TCP_accept_new(TCP_Server *TCP_server) +{ + uint32_t i; + + for (i = 0; i < TCP_server->num_listening_socks; ++i) { + struct sockaddr_storage addr; + socklen_t addrlen = sizeof(addr); + Socket sock; + + do { + sock = accept(TCP_server->socks_listening[i], (struct sockaddr *)&addr, &addrlen); + } while (accept_connection(TCP_server, sock) != -1); + } +} + +static int do_incoming(TCP_Server *TCP_server, uint32_t i) +{ + if (TCP_server->incoming_connection_queue[i].status != TCP_STATUS_CONNECTED) { + return -1; + } + + int ret = read_connection_handshake(&TCP_server->incoming_connection_queue[i], TCP_server->secret_key); + + if (ret == -1) { + kill_TCP_secure_connection(&TCP_server->incoming_connection_queue[i]); + } else if (ret == 1) { + int index_new = TCP_server->unconfirmed_connection_queue_index % MAX_INCOMING_CONNECTIONS; + TCP_Secure_Connection *conn_old = &TCP_server->incoming_connection_queue[i]; + TCP_Secure_Connection *conn_new = &TCP_server->unconfirmed_connection_queue[index_new]; + + if (conn_new->status != TCP_STATUS_NO_STATUS) { + kill_TCP_secure_connection(conn_new); + } + + memcpy(conn_new, conn_old, sizeof(TCP_Secure_Connection)); + crypto_memzero(conn_old, sizeof(TCP_Secure_Connection)); + ++TCP_server->unconfirmed_connection_queue_index; + + return index_new; + } + + return -1; +} + +static int do_unconfirmed(TCP_Server *TCP_server, uint32_t i) +{ + TCP_Secure_Connection *conn = &TCP_server->unconfirmed_connection_queue[i]; + + if (conn->status != TCP_STATUS_UNCONFIRMED) { + return -1; + } + + uint8_t packet[MAX_PACKET_SIZE]; + int len = read_packet_TCP_secure_connection(conn->sock, &conn->next_packet_length, conn->shared_key, conn->recv_nonce, + packet, sizeof(packet)); + + if (len == 0) { + return -1; + } + + if (len == -1) { + kill_TCP_secure_connection(conn); + return -1; + } + + return confirm_TCP_connection(TCP_server, conn, packet, len); +} + +static void do_confirmed_recv(TCP_Server *TCP_server, uint32_t i) +{ + TCP_Secure_Connection *conn = &TCP_server->accepted_connection_array[i]; + + uint8_t packet[MAX_PACKET_SIZE]; + int len; + + while ((len = read_packet_TCP_secure_connection(conn->sock, &conn->next_packet_length, conn->shared_key, + conn->recv_nonce, packet, sizeof(packet)))) { + if (len == -1) { + kill_accepted(TCP_server, i); + break; + } + + if (handle_TCP_packet(TCP_server, i, packet, len) == -1) { + kill_accepted(TCP_server, i); + break; + } + } +} + +static void do_TCP_incoming(TCP_Server *TCP_server) +{ + uint32_t i; + + for (i = 0; i < MAX_INCOMING_CONNECTIONS; ++i) { + do_incoming(TCP_server, i); + } +} + +static void do_TCP_unconfirmed(TCP_Server *TCP_server) +{ + uint32_t i; + + for (i = 0; i < MAX_INCOMING_CONNECTIONS; ++i) { + do_unconfirmed(TCP_server, i); + } +} + +static void do_TCP_confirmed(TCP_Server *TCP_server) +{ +#ifdef TCP_SERVER_USE_EPOLL + + if (TCP_server->last_run_pinged == unix_time()) { + return; + } + + TCP_server->last_run_pinged = unix_time(); +#endif + uint32_t i; + + for (i = 0; i < TCP_server->size_accepted_connections; ++i) { + TCP_Secure_Connection *conn = &TCP_server->accepted_connection_array[i]; + + if (conn->status != TCP_STATUS_CONFIRMED) { + continue; + } + + if (is_timeout(conn->last_pinged, TCP_PING_FREQUENCY)) { + uint8_t ping[1 + sizeof(uint64_t)]; + ping[0] = TCP_PACKET_PING; + uint64_t ping_id = random_64b(); + + if (!ping_id) { + ++ping_id; + } + + memcpy(ping + 1, &ping_id, sizeof(uint64_t)); + int ret = write_packet_TCP_secure_connection(conn, ping, sizeof(ping), 1); + + if (ret == 1) { + conn->last_pinged = unix_time(); + conn->ping_id = ping_id; + } else { + if (is_timeout(conn->last_pinged, TCP_PING_FREQUENCY + TCP_PING_TIMEOUT)) { + kill_accepted(TCP_server, i); + continue; + } + } + } + + if (conn->ping_id && is_timeout(conn->last_pinged, TCP_PING_TIMEOUT)) { + kill_accepted(TCP_server, i); + continue; + } + + send_pending_data(conn); + +#ifndef TCP_SERVER_USE_EPOLL + + do_confirmed_recv(TCP_server, i); + +#endif + } +} + +#ifdef TCP_SERVER_USE_EPOLL +static void do_TCP_epoll(TCP_Server *TCP_server) +{ +#define MAX_EVENTS 16 + struct epoll_event events[MAX_EVENTS]; + int nfds; + + while ((nfds = epoll_wait(TCP_server->efd, events, MAX_EVENTS, 0)) > 0) { + int n; + + for (n = 0; n < nfds; ++n) { + Socket sock = events[n].data.u64 & 0xFFFFFFFF; + int status = (events[n].data.u64 >> 32) & 0xFF, index = (events[n].data.u64 >> 40); + + if ((events[n].events & EPOLLERR) || (events[n].events & EPOLLHUP) || (events[n].events & EPOLLRDHUP)) { + switch (status) { + case TCP_SOCKET_LISTENING: { + //should never happen + break; + } + + case TCP_SOCKET_INCOMING: { + kill_TCP_secure_connection(&TCP_server->incoming_connection_queue[index]); + break; + } + + case TCP_SOCKET_UNCONFIRMED: { + kill_TCP_secure_connection(&TCP_server->unconfirmed_connection_queue[index]); + break; + } + + case TCP_SOCKET_CONFIRMED: { + kill_accepted(TCP_server, index); + break; + } + } + + continue; + } + + + if (!(events[n].events & EPOLLIN)) { + continue; + } + + switch (status) { + case TCP_SOCKET_LISTENING: { + //socket is from socks_listening, accept connection + struct sockaddr_storage addr; + socklen_t addrlen = sizeof(addr); + + while (1) { + Socket sock_new = accept(sock, (struct sockaddr *)&addr, &addrlen); + + if (!sock_valid(sock_new)) { + break; + } + + int index_new = accept_connection(TCP_server, sock_new); + + if (index_new == -1) { + continue; + } + + struct epoll_event ev = { + .events = EPOLLIN | EPOLLET | EPOLLRDHUP, + .data.u64 = sock_new | ((uint64_t)TCP_SOCKET_INCOMING << 32) | ((uint64_t)index_new << 40) + }; + + if (epoll_ctl(TCP_server->efd, EPOLL_CTL_ADD, sock_new, &ev) == -1) { + kill_TCP_secure_connection(&TCP_server->incoming_connection_queue[index_new]); + continue; + } + } + + break; + } + + case TCP_SOCKET_INCOMING: { + int index_new; + + if ((index_new = do_incoming(TCP_server, index)) != -1) { + events[n].events = EPOLLIN | EPOLLET | EPOLLRDHUP; + events[n].data.u64 = sock | ((uint64_t)TCP_SOCKET_UNCONFIRMED << 32) | ((uint64_t)index_new << 40); + + if (epoll_ctl(TCP_server->efd, EPOLL_CTL_MOD, sock, &events[n]) == -1) { + kill_TCP_secure_connection(&TCP_server->unconfirmed_connection_queue[index_new]); + break; + } + } + + break; + } + + case TCP_SOCKET_UNCONFIRMED: { + int index_new; + + if ((index_new = do_unconfirmed(TCP_server, index)) != -1) { + events[n].events = EPOLLIN | EPOLLET | EPOLLRDHUP; + events[n].data.u64 = sock | ((uint64_t)TCP_SOCKET_CONFIRMED << 32) | ((uint64_t)index_new << 40); + + if (epoll_ctl(TCP_server->efd, EPOLL_CTL_MOD, sock, &events[n]) == -1) { + //remove from confirmed connections + kill_accepted(TCP_server, index_new); + break; + } + } + + break; + } + + case TCP_SOCKET_CONFIRMED: { + do_confirmed_recv(TCP_server, index); + break; + } + } + } + } + +#undef MAX_EVENTS +} +#endif + +void do_TCP_server(TCP_Server *TCP_server) +{ + unix_time_update(); + +#ifdef TCP_SERVER_USE_EPOLL + do_TCP_epoll(TCP_server); + +#else + do_TCP_accept_new(TCP_server); + do_TCP_incoming(TCP_server); + do_TCP_unconfirmed(TCP_server); +#endif + + do_TCP_confirmed(TCP_server); +} + +void kill_TCP_server(TCP_Server *TCP_server) +{ + uint32_t i; + + for (i = 0; i < TCP_server->num_listening_socks; ++i) { + kill_sock(TCP_server->socks_listening[i]); + } + + if (TCP_server->onion) { + set_callback_handle_recv_1(TCP_server->onion, NULL, NULL); + } + + bs_list_free(&TCP_server->accepted_key_list); + +#ifdef TCP_SERVER_USE_EPOLL + close(TCP_server->efd); +#endif + + free(TCP_server->socks_listening); + free(TCP_server->accepted_connection_array); + free(TCP_server); +} diff --git a/libs/libtox/src/toxcore/TCP_server.h b/libs/libtox/src/toxcore/TCP_server.h new file mode 100644 index 0000000000..f213c078e6 --- /dev/null +++ b/libs/libtox/src/toxcore/TCP_server.h @@ -0,0 +1,167 @@ +/* + * Implementation of the TCP relay server part of Tox. + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2014 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifndef TCP_SERVER_H +#define TCP_SERVER_H + +#include "crypto_core.h" +#include "list.h" +#include "onion.h" + +#ifdef TCP_SERVER_USE_EPOLL +#include <sys/epoll.h> +#endif + +// Disable MSG_NOSIGNAL on systems not supporting it, e.g. Windows, FreeBSD +#if !defined(MSG_NOSIGNAL) +#define MSG_NOSIGNAL 0 +#endif + +#define MAX_INCOMING_CONNECTIONS 256 + +#define TCP_MAX_BACKLOG MAX_INCOMING_CONNECTIONS + +#define MAX_PACKET_SIZE 2048 + +#define TCP_HANDSHAKE_PLAIN_SIZE (CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE) +#define TCP_SERVER_HANDSHAKE_SIZE (CRYPTO_NONCE_SIZE + TCP_HANDSHAKE_PLAIN_SIZE + CRYPTO_MAC_SIZE) +#define TCP_CLIENT_HANDSHAKE_SIZE (CRYPTO_PUBLIC_KEY_SIZE + TCP_SERVER_HANDSHAKE_SIZE) +#define TCP_MAX_OOB_DATA_LENGTH 1024 + +#define NUM_RESERVED_PORTS 16 +#define NUM_CLIENT_CONNECTIONS (256 - NUM_RESERVED_PORTS) + +#define TCP_PACKET_ROUTING_REQUEST 0 +#define TCP_PACKET_ROUTING_RESPONSE 1 +#define TCP_PACKET_CONNECTION_NOTIFICATION 2 +#define TCP_PACKET_DISCONNECT_NOTIFICATION 3 +#define TCP_PACKET_PING 4 +#define TCP_PACKET_PONG 5 +#define TCP_PACKET_OOB_SEND 6 +#define TCP_PACKET_OOB_RECV 7 +#define TCP_PACKET_ONION_REQUEST 8 +#define TCP_PACKET_ONION_RESPONSE 9 + +#define ARRAY_ENTRY_SIZE 6 + +/* frequency to ping connected nodes and timeout in seconds */ +#define TCP_PING_FREQUENCY 30 +#define TCP_PING_TIMEOUT 10 + +#ifdef TCP_SERVER_USE_EPOLL +#define TCP_SOCKET_LISTENING 0 +#define TCP_SOCKET_INCOMING 1 +#define TCP_SOCKET_UNCONFIRMED 2 +#define TCP_SOCKET_CONFIRMED 3 +#endif + +enum { + TCP_STATUS_NO_STATUS, + TCP_STATUS_CONNECTED, + TCP_STATUS_UNCONFIRMED, + TCP_STATUS_CONFIRMED, +}; + +typedef struct TCP_Priority_List TCP_Priority_List; + +struct TCP_Priority_List { + TCP_Priority_List *next; + uint16_t size, sent; + uint8_t data[]; +}; + +typedef struct TCP_Secure_Connection { + Socket sock; + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t recv_nonce[CRYPTO_NONCE_SIZE]; /* Nonce of received packets. */ + uint8_t sent_nonce[CRYPTO_NONCE_SIZE]; /* Nonce of sent packets. */ + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + uint16_t next_packet_length; + struct { + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint32_t index; + uint8_t status; /* 0 if not used, 1 if other is offline, 2 if other is online. */ + uint8_t other_id; + } connections[NUM_CLIENT_CONNECTIONS]; + uint8_t last_packet[2 + MAX_PACKET_SIZE]; + uint8_t status; + uint16_t last_packet_length; + uint16_t last_packet_sent; + + TCP_Priority_List *priority_queue_start, *priority_queue_end; + + uint64_t identifier; + + uint64_t last_pinged; + uint64_t ping_id; +} TCP_Secure_Connection; + + +typedef struct TCP_Server TCP_Server; + +const uint8_t *tcp_server_public_key(const TCP_Server *tcp_server); +size_t tcp_server_listen_count(const TCP_Server *tcp_server); + +/* Create new TCP server instance. + */ +TCP_Server *new_TCP_server(uint8_t ipv6_enabled, uint16_t num_sockets, const uint16_t *ports, const uint8_t *secret_key, + Onion *onion); + +/* Run the TCP_server + */ +void do_TCP_server(TCP_Server *TCP_server); + +/* Kill the TCP server + */ +void kill_TCP_server(TCP_Server *TCP_server); + +/* return the amount of data in the tcp recv buffer. + * return 0 on failure. + */ +unsigned int TCP_socket_data_recv_buffer(Socket sock); + +/* Read the next two bytes in TCP stream then convert them to + * length (host byte order). + * + * return length on success + * return 0 if nothing has been read from socket. + * return ~0 on failure. + */ +uint16_t read_TCP_length(Socket sock); + +/* Read length bytes from socket. + * + * return length on success + * return -1 on failure/no data in buffer. + */ +int read_TCP_packet(Socket sock, uint8_t *data, uint16_t length); + +/* return length of received packet on success. + * return 0 if could not read any packet. + * return -1 on failure (connection must be killed). + */ +int read_packet_TCP_secure_connection(Socket sock, uint16_t *next_packet_length, const uint8_t *shared_key, + uint8_t *recv_nonce, uint8_t *data, uint16_t max_len); + + +#endif diff --git a/libs/libtox/src/toxcore/ccompat.h b/libs/libtox/src/toxcore/ccompat.h new file mode 100644 index 0000000000..e72e66ae58 --- /dev/null +++ b/libs/libtox/src/toxcore/ccompat.h @@ -0,0 +1,43 @@ +/* + * C language compatibility macros for varying compiler support. + */ +#ifndef CCOMPAT_H +#define CCOMPAT_H + +// Marking GNU extensions to avoid warnings. +#if defined(__GNUC__) +#define GNU_EXTENSION __extension__ +#else +#define GNU_EXTENSION +#endif + +// Variable length arrays. +// VLA(type, name, size) allocates a variable length array with automatic +// storage duration. VLA_SIZE(name) evaluates to the runtime size of that array +// in bytes. +// +// If C99 VLAs are not available, an emulation using alloca (stack allocation +// "function") is used. Note the semantic difference: alloca'd memory does not +// get freed at the end of the declaration's scope. Do not use VLA() in loops or +// you may run out of stack space. +#if !defined(_MSC_VER) && __STDC_VERSION__ >= 199901L +// C99 VLAs. +#define VLA(type, name, size) type name[size] +#define SIZEOF_VLA sizeof +#else + +// Emulation using alloca. +#ifdef _WIN32 +#include <malloc.h> +#else +#include <alloca.h> +#endif + +#define VLA(type, name, size) \ + const size_t name##_size = (size) * sizeof(type); \ + type *const name = (type *)alloca(name##_size) +#define SIZEOF_VLA(name) name##_size + +#endif + +#endif /* CCOMPAT_H */ diff --git a/libs/libtox/src/toxcore/crypto_core.api.h b/libs/libtox/src/toxcore/crypto_core.api.h new file mode 100644 index 0000000000..cef1a52c14 --- /dev/null +++ b/libs/libtox/src/toxcore/crypto_core.api.h @@ -0,0 +1,246 @@ +%{ +/* + * Functions for the core crypto. + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2013 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifndef CRYPTO_CORE_H +#define CRYPTO_CORE_H + +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> +%} + +/** + * The number of bytes in a Tox public key. + */ +const CRYPTO_PUBLIC_KEY_SIZE = 32; + +/** + * The number of bytes in a Tox secret key. + */ +const CRYPTO_SECRET_KEY_SIZE = 32; + +/** + * The number of bytes in a shared key computed from public and secret keys. + */ +const CRYPTO_SHARED_KEY_SIZE = 32; + +/** + * The number of bytes in a symmetric key. + */ +const CRYPTO_SYMMETRIC_KEY_SIZE = CRYPTO_SHARED_KEY_SIZE; + +/** + * The number of bytes needed for the MAC (message authentication code) in an + * encrypted message. + */ +const CRYPTO_MAC_SIZE = 16; + +/** + * The number of bytes in a nonce used for encryption/decryption. + */ +const CRYPTO_NONCE_SIZE = 24; + +/** + * The number of bytes in a SHA256 hash. + */ +const CRYPTO_SHA256_SIZE = 32; + +/** + * The number of bytes in a SHA512 hash. + */ +const CRYPTO_SHA512_SIZE = 64; + +/** + * A `memcmp`-like function whose running time does not depend on the input + * bytes, only on the input length. Useful to compare sensitive data where + * timing attacks could reveal that data. + * + * This means for instance that comparing "aaaa" and "aaaa" takes 4 time, and + * "aaaa" and "baaa" also takes 4 time. With a regular `memcmp`, the latter may + * take 1 time, because it immediately knows that the two strings are not equal. + */ +static int32_t crypto_memcmp(const void *p1, const void *p2, size_t length); + +/** + * A `bzero`-like function which won't be optimised away by the compiler. Some + * compilers will inline `bzero` or `memset` if they can prove that there will + * be no reads to the written data. Use this function if you want to be sure the + * memory is indeed zeroed. + */ +static void crypto_memzero(void *data, size_t length); + +/** + * Compute a SHA256 hash (32 bytes). + */ +static void crypto_sha256(uint8_t[CRYPTO_SHA256_SIZE] hash, const uint8_t[length] data); + +/** + * Compute a SHA512 hash (64 bytes). + */ +static void crypto_sha512(uint8_t[CRYPTO_SHA512_SIZE] hash, const uint8_t[length] data); + +/** + * Compare 2 public keys of length CRYPTO_PUBLIC_KEY_SIZE, not vulnerable to + * timing attacks. + * + * @return 0 if both mem locations of length are equal, -1 if they are not. + */ +static int32_t public_key_cmp( + const uint8_t[CRYPTO_PUBLIC_KEY_SIZE] pk1, + const uint8_t[CRYPTO_PUBLIC_KEY_SIZE] pk2); + +/** + * Return a random 32 bit integer. + */ +static uint32_t random_int(); + +/** + * Return a random 64 bit integer. + */ +static uint64_t random_64b(); + +/** + * Check if a Tox public key CRYPTO_PUBLIC_KEY_SIZE is valid or not. This + * should only be used for input validation. + * + * @return false if it isn't, true if it is. + */ +static bool public_key_valid(const uint8_t[CRYPTO_PUBLIC_KEY_SIZE] public_key); + +/** + * Generate a new random keypair. Every call to this function is likely to + * generate a different keypair. + */ +static int32_t crypto_new_keypair( + uint8_t[CRYPTO_PUBLIC_KEY_SIZE] public_key, + uint8_t[CRYPTO_SECRET_KEY_SIZE] secret_key); + +/** + * Derive the public key from a given secret key. + */ +static void crypto_derive_public_key( + uint8_t[CRYPTO_PUBLIC_KEY_SIZE] public_key, + const uint8_t[CRYPTO_SECRET_KEY_SIZE] secret_key); + +/** + * Encrypt plain text of the given length to encrypted of length + + * $CRYPTO_MAC_SIZE using the public key ($CRYPTO_PUBLIC_KEY_SIZE bytes) of the + * receiver and the secret key of the sender and a $CRYPTO_NONCE_SIZE byte + * nonce. + * + * @return -1 if there was a problem, length of encrypted data if everything + * was fine. + */ +static int32_t encrypt_data( + const uint8_t[CRYPTO_PUBLIC_KEY_SIZE] public_key, + const uint8_t[CRYPTO_SECRET_KEY_SIZE] secret_key, + const uint8_t[CRYPTO_NONCE_SIZE] nonce, + const uint8_t[length] plain, + uint8_t *encrypted); + + +/** + * Decrypt encrypted text of the given length to plain text of the given length + * - $CRYPTO_MAC_SIZE using the public key ($CRYPTO_PUBLIC_KEY_SIZE bytes) of + * the sender, the secret key of the receiver and a $CRYPTO_NONCE_SIZE byte + * nonce. + * + * @return -1 if there was a problem (decryption failed), length of plain text + * data if everything was fine. + */ +static int32_t decrypt_data( + const uint8_t[CRYPTO_PUBLIC_KEY_SIZE] public_key, + const uint8_t[CRYPTO_SECRET_KEY_SIZE] secret_key, + const uint8_t[CRYPTO_NONCE_SIZE] nonce, + const uint8_t[length] encrypted, + uint8_t *plain); + +/** + * Fast encrypt/decrypt operations. Use if this is not a one-time communication. + * $encrypt_precompute does the shared-key generation once so it does not have + * to be preformed on every encrypt/decrypt. + */ +static int32_t encrypt_precompute( + const uint8_t[CRYPTO_PUBLIC_KEY_SIZE] public_key, + const uint8_t[CRYPTO_SECRET_KEY_SIZE] secret_key, + uint8_t[CRYPTO_SHARED_KEY_SIZE] shared_key); + +/** + * Encrypts plain of length length to encrypted of length + $CRYPTO_MAC_SIZE + * using a shared key $CRYPTO_SYMMETRIC_KEY_SIZE big and a $CRYPTO_NONCE_SIZE + * byte nonce. + * + * @return -1 if there was a problem, length of encrypted data if everything + * was fine. + */ +static int32_t encrypt_data_symmetric( + const uint8_t[CRYPTO_SHARED_KEY_SIZE] shared_key, + const uint8_t[CRYPTO_NONCE_SIZE] nonce, + const uint8_t[length] plain, + uint8_t *encrypted); + +/** + * Decrypts encrypted of length length to plain of length length - + * $CRYPTO_MAC_SIZE using a shared key CRYPTO_SHARED_KEY_SIZE big and a + * $CRYPTO_NONCE_SIZE byte nonce. + * + * @return -1 if there was a problem (decryption failed), length of plain data + * if everything was fine. + */ +static int32_t decrypt_data_symmetric( + const uint8_t[CRYPTO_SHARED_KEY_SIZE] shared_key, + const uint8_t[CRYPTO_NONCE_SIZE] nonce, + const uint8_t[length] encrypted, + uint8_t *plain); + +/** + * Increment the given nonce by 1 in big endian (rightmost byte incremented + * first). + */ +static void increment_nonce(uint8_t[CRYPTO_NONCE_SIZE] nonce); + +/** + * Increment the given nonce by a given number. The number should be in host + * byte order. + */ +static void increment_nonce_number(uint8_t[CRYPTO_NONCE_SIZE] nonce, uint32_t host_order_num); + +/** + * Fill the given nonce with random bytes. + */ +static void random_nonce(uint8_t[CRYPTO_NONCE_SIZE] nonce); + +/** + * Fill a key CRYPTO_SYMMETRIC_KEY_SIZE big with random bytes. + */ +static void new_symmetric_key(uint8_t[CRYPTO_SYMMETRIC_KEY_SIZE] key); + +/** + * Fill an array of bytes with random values. + */ +static void random_bytes(uint8_t[length] bytes); + +%{ +#endif /* CRYPTO_CORE_H */ +%} diff --git a/libs/libtox/src/toxcore/crypto_core.c b/libs/libtox/src/toxcore/crypto_core.c new file mode 100644 index 0000000000..8e34b5876a --- /dev/null +++ b/libs/libtox/src/toxcore/crypto_core.c @@ -0,0 +1,287 @@ +/* + * Functions for the core crypto. + * + * NOTE: This code has to be perfect. We don't mess around with encryption. + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2013 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ccompat.h" +#include "crypto_core.h" + +#include <string.h> + +#ifndef VANILLA_NACL +/* We use libsodium by default. */ +#include <sodium.h> +#else +#include <crypto_box.h> +#include <crypto_hash_sha256.h> +#include <crypto_hash_sha512.h> +#include <crypto_scalarmult_curve25519.h> +#include <crypto_verify_16.h> +#include <crypto_verify_32.h> +#include <randombytes.h> +#define crypto_box_MACBYTES (crypto_box_ZEROBYTES - crypto_box_BOXZEROBYTES) +#endif + +#if CRYPTO_PUBLIC_KEY_SIZE != crypto_box_PUBLICKEYBYTES +#error CRYPTO_PUBLIC_KEY_SIZE should be equal to crypto_box_PUBLICKEYBYTES +#endif + +#if CRYPTO_SECRET_KEY_SIZE != crypto_box_SECRETKEYBYTES +#error CRYPTO_SECRET_KEY_SIZE should be equal to crypto_box_SECRETKEYBYTES +#endif + +#if CRYPTO_SHARED_KEY_SIZE != crypto_box_BEFORENMBYTES +#error CRYPTO_SHARED_KEY_SIZE should be equal to crypto_box_BEFORENMBYTES +#endif + +#if CRYPTO_SYMMETRIC_KEY_SIZE != crypto_box_BEFORENMBYTES +#error CRYPTO_SYMMETRIC_KEY_SIZE should be equal to crypto_box_BEFORENMBYTES +#endif + +#if CRYPTO_MAC_SIZE != crypto_box_MACBYTES +#error CRYPTO_MAC_SIZE should be equal to crypto_box_MACBYTES +#endif + +#if CRYPTO_NONCE_SIZE != crypto_box_NONCEBYTES +#error CRYPTO_NONCE_SIZE should be equal to crypto_box_NONCEBYTES +#endif + +#if CRYPTO_SHA256_SIZE != crypto_hash_sha256_BYTES +#error CRYPTO_SHA256_SIZE should be equal to crypto_hash_sha256_BYTES +#endif + +#if CRYPTO_SHA512_SIZE != crypto_hash_sha512_BYTES +#error CRYPTO_SHA512_SIZE should be equal to crypto_hash_sha512_BYTES +#endif + +int32_t public_key_cmp(const uint8_t *pk1, const uint8_t *pk2) +{ +#if CRYPTO_PUBLIC_KEY_SIZE != 32 +#error CRYPTO_PUBLIC_KEY_SIZE is required to be 32 bytes for public_key_cmp to work, +#endif + return crypto_verify_32(pk1, pk2); +} + +uint32_t random_int(void) +{ + uint32_t randnum; + randombytes((uint8_t *)&randnum , sizeof(randnum)); + return randnum; +} + +uint64_t random_64b(void) +{ + uint64_t randnum; + randombytes((uint8_t *)&randnum, sizeof(randnum)); + return randnum; +} + +bool public_key_valid(const uint8_t *public_key) +{ + if (public_key[31] >= 128) { /* Last bit of key is always zero. */ + return 0; + } + + return 1; +} + +/* Precomputes the shared key from their public_key and our secret_key. + * This way we can avoid an expensive elliptic curve scalar multiply for each + * encrypt/decrypt operation. + * shared_key has to be crypto_box_BEFORENMBYTES bytes long. + */ +int32_t encrypt_precompute(const uint8_t *public_key, const uint8_t *secret_key, uint8_t *shared_key) +{ + return crypto_box_beforenm(shared_key, public_key, secret_key); +} + +int32_t encrypt_data_symmetric(const uint8_t *secret_key, const uint8_t *nonce, const uint8_t *plain, size_t length, + uint8_t *encrypted) +{ + if (length == 0 || !secret_key || !nonce || !plain || !encrypted) { + return -1; + } + + VLA(uint8_t, temp_plain, length + crypto_box_ZEROBYTES); + VLA(uint8_t, temp_encrypted, length + crypto_box_MACBYTES + crypto_box_BOXZEROBYTES); + + memset(temp_plain, 0, crypto_box_ZEROBYTES); + memcpy(temp_plain + crypto_box_ZEROBYTES, plain, length); // Pad the message with 32 0 bytes. + + if (crypto_box_afternm(temp_encrypted, temp_plain, length + crypto_box_ZEROBYTES, nonce, secret_key) != 0) { + return -1; + } + + /* Unpad the encrypted message. */ + memcpy(encrypted, temp_encrypted + crypto_box_BOXZEROBYTES, length + crypto_box_MACBYTES); + return length + crypto_box_MACBYTES; +} + +int32_t decrypt_data_symmetric(const uint8_t *secret_key, const uint8_t *nonce, const uint8_t *encrypted, size_t length, + uint8_t *plain) +{ + if (length <= crypto_box_BOXZEROBYTES || !secret_key || !nonce || !encrypted || !plain) { + return -1; + } + + VLA(uint8_t, temp_plain, length + crypto_box_ZEROBYTES); + VLA(uint8_t, temp_encrypted, length + crypto_box_BOXZEROBYTES); + + memset(temp_encrypted, 0, crypto_box_BOXZEROBYTES); + memcpy(temp_encrypted + crypto_box_BOXZEROBYTES, encrypted, length); // Pad the message with 16 0 bytes. + + if (crypto_box_open_afternm(temp_plain, temp_encrypted, length + crypto_box_BOXZEROBYTES, nonce, secret_key) != 0) { + return -1; + } + + memcpy(plain, temp_plain + crypto_box_ZEROBYTES, length - crypto_box_MACBYTES); + return length - crypto_box_MACBYTES; +} + +int32_t encrypt_data(const uint8_t *public_key, const uint8_t *secret_key, const uint8_t *nonce, + const uint8_t *plain, size_t length, uint8_t *encrypted) +{ + if (!public_key || !secret_key) { + return -1; + } + + uint8_t k[crypto_box_BEFORENMBYTES]; + encrypt_precompute(public_key, secret_key, k); + int ret = encrypt_data_symmetric(k, nonce, plain, length, encrypted); + crypto_memzero(k, sizeof k); + return ret; +} + +int32_t decrypt_data(const uint8_t *public_key, const uint8_t *secret_key, const uint8_t *nonce, + const uint8_t *encrypted, size_t length, uint8_t *plain) +{ + if (!public_key || !secret_key) { + return -1; + } + + uint8_t k[crypto_box_BEFORENMBYTES]; + encrypt_precompute(public_key, secret_key, k); + int ret = decrypt_data_symmetric(k, nonce, encrypted, length, plain); + crypto_memzero(k, sizeof k); + return ret; +} + + +/* Increment the given nonce by 1. */ +void increment_nonce(uint8_t *nonce) +{ + /* TODO(irungentoo): use increment_nonce_number(nonce, 1) or sodium_increment (change to little endian) + * NOTE don't use breaks inside this loop + * In particular, make sure, as far as possible, + * that loop bounds and their potential underflow or overflow + * are independent of user-controlled input (you may have heard of the Heartbleed bug). + */ + uint32_t i = crypto_box_NONCEBYTES; + uint_fast16_t carry = 1U; + + for (; i != 0; --i) { + carry += (uint_fast16_t) nonce[i - 1]; + nonce[i - 1] = (uint8_t) carry; + carry >>= 8; + } +} + +static uint32_t host_to_network(uint32_t x) +{ +#if BYTE_ORDER == LITTLE_ENDIAN + return + ((x >> 24) & 0x000000FF) | // move byte 3 to byte 0 + ((x >> 8) & 0x0000FF00) | // move byte 2 to byte 1 + ((x << 8) & 0x00FF0000) | // move byte 1 to byte 2 + ((x << 24) & 0xFF000000); // move byte 0 to byte 3 +#else + return x; +#endif +} + +/* increment the given nonce by num */ +void increment_nonce_number(uint8_t *nonce, uint32_t host_order_num) +{ + /* NOTE don't use breaks inside this loop + * In particular, make sure, as far as possible, + * that loop bounds and their potential underflow or overflow + * are independent of user-controlled input (you may have heard of the Heartbleed bug). + */ + const uint32_t big_endian_num = host_to_network(host_order_num); + const uint8_t *const num_vec = (const uint8_t *) &big_endian_num; + uint8_t num_as_nonce[crypto_box_NONCEBYTES] = {0}; + num_as_nonce[crypto_box_NONCEBYTES - 4] = num_vec[0]; + num_as_nonce[crypto_box_NONCEBYTES - 3] = num_vec[1]; + num_as_nonce[crypto_box_NONCEBYTES - 2] = num_vec[2]; + num_as_nonce[crypto_box_NONCEBYTES - 1] = num_vec[3]; + + uint32_t i = crypto_box_NONCEBYTES; + uint_fast16_t carry = 0U; + + for (; i != 0; --i) { + carry += (uint_fast16_t) nonce[i - 1] + (uint_fast16_t) num_as_nonce[i - 1]; + nonce[i - 1] = (unsigned char) carry; + carry >>= 8; + } +} + +/* Fill the given nonce with random bytes. */ +void random_nonce(uint8_t *nonce) +{ + randombytes(nonce, crypto_box_NONCEBYTES); +} + +/* Fill a key CRYPTO_SYMMETRIC_KEY_SIZE big with random bytes */ +void new_symmetric_key(uint8_t *key) +{ + randombytes(key, CRYPTO_SYMMETRIC_KEY_SIZE); +} + +int32_t crypto_new_keypair(uint8_t *public_key, uint8_t *secret_key) +{ + return crypto_box_keypair(public_key, secret_key); +} + +void crypto_derive_public_key(uint8_t *public_key, const uint8_t *secret_key) +{ + crypto_scalarmult_curve25519_base(public_key, secret_key); +} + +void crypto_sha256(uint8_t *hash, const uint8_t *data, size_t length) +{ + crypto_hash_sha256(hash, data, length); +} + +void crypto_sha512(uint8_t *hash, const uint8_t *data, size_t length) +{ + crypto_hash_sha512(hash, data, length); +} + +void random_bytes(uint8_t *data, size_t length) +{ + randombytes(data, length); +} diff --git a/libs/libtox/src/toxcore/crypto_core.h b/libs/libtox/src/toxcore/crypto_core.h new file mode 100644 index 0000000000..fc5756c1d2 --- /dev/null +++ b/libs/libtox/src/toxcore/crypto_core.h @@ -0,0 +1,234 @@ +/* + * Functions for the core crypto. + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2013 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifndef CRYPTO_CORE_H +#define CRYPTO_CORE_H + +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> + +/** + * The number of bytes in a Tox public key. + */ +#define CRYPTO_PUBLIC_KEY_SIZE 32 + +uint32_t crypto_public_key_size(void); + +/** + * The number of bytes in a Tox secret key. + */ +#define CRYPTO_SECRET_KEY_SIZE 32 + +uint32_t crypto_secret_key_size(void); + +/** + * The number of bytes in a shared key computed from public and secret keys. + */ +#define CRYPTO_SHARED_KEY_SIZE 32 + +uint32_t crypto_shared_key_size(void); + +/** + * The number of bytes in a symmetric key. + */ +#define CRYPTO_SYMMETRIC_KEY_SIZE CRYPTO_SHARED_KEY_SIZE + +uint32_t crypto_symmetric_key_size(void); + +/** + * The number of bytes needed for the MAC (message authentication code) in an + * encrypted message. + */ +#define CRYPTO_MAC_SIZE 16 + +uint32_t crypto_mac_size(void); + +/** + * The number of bytes in a nonce used for encryption/decryption. + */ +#define CRYPTO_NONCE_SIZE 24 + +uint32_t crypto_nonce_size(void); + +/** + * The number of bytes in a SHA256 hash. + */ +#define CRYPTO_SHA256_SIZE 32 + +uint32_t crypto_sha256_size(void); + +/** + * The number of bytes in a SHA512 hash. + */ +#define CRYPTO_SHA512_SIZE 64 + +uint32_t crypto_sha512_size(void); + +/** + * A `memcmp`-like function whose running time does not depend on the input + * bytes, only on the input length. Useful to compare sensitive data where + * timing attacks could reveal that data. + * + * This means for instance that comparing "aaaa" and "aaaa" takes 4 time, and + * "aaaa" and "baaa" also takes 4 time. With a regular `memcmp`, the latter may + * take 1 time, because it immediately knows that the two strings are not equal. + */ +int32_t crypto_memcmp(const void *p1, const void *p2, size_t length); + +/** + * A `bzero`-like function which won't be optimised away by the compiler. Some + * compilers will inline `bzero` or `memset` if they can prove that there will + * be no reads to the written data. Use this function if you want to be sure the + * memory is indeed zeroed. + */ +void crypto_memzero(void *data, size_t length); + +/** + * Compute a SHA256 hash (32 bytes). + */ +void crypto_sha256(uint8_t *hash, const uint8_t *data, size_t length); + +/** + * Compute a SHA512 hash (64 bytes). + */ +void crypto_sha512(uint8_t *hash, const uint8_t *data, size_t length); + +/** + * Compare 2 public keys of length CRYPTO_PUBLIC_KEY_SIZE, not vulnerable to + * timing attacks. + * + * @return 0 if both mem locations of length are equal, -1 if they are not. + */ +int32_t public_key_cmp(const uint8_t *pk1, const uint8_t *pk2); + +/** + * Return a random 32 bit integer. + */ +uint32_t random_int(void); + +/** + * Return a random 64 bit integer. + */ +uint64_t random_64b(void); + +/** + * Check if a Tox public key CRYPTO_PUBLIC_KEY_SIZE is valid or not. This + * should only be used for input validation. + * + * @return false if it isn't, true if it is. + */ +bool public_key_valid(const uint8_t *public_key); + +/** + * Generate a new random keypair. Every call to this function is likely to + * generate a different keypair. + */ +int32_t crypto_new_keypair(uint8_t *public_key, uint8_t *secret_key); + +/** + * Derive the public key from a given secret key. + */ +void crypto_derive_public_key(uint8_t *public_key, const uint8_t *secret_key); + +/** + * Encrypt plain text of the given length to encrypted of length + + * CRYPTO_MAC_SIZE using the public key (CRYPTO_PUBLIC_KEY_SIZE bytes) of the + * receiver and the secret key of the sender and a CRYPTO_NONCE_SIZE byte + * nonce. + * + * @return -1 if there was a problem, length of encrypted data if everything + * was fine. + */ +int32_t encrypt_data(const uint8_t *public_key, const uint8_t *secret_key, const uint8_t *nonce, const uint8_t *plain, + size_t length, uint8_t *encrypted); + +/** + * Decrypt encrypted text of the given length to plain text of the given length + * - CRYPTO_MAC_SIZE using the public key (CRYPTO_PUBLIC_KEY_SIZE bytes) of + * the sender, the secret key of the receiver and a CRYPTO_NONCE_SIZE byte + * nonce. + * + * @return -1 if there was a problem (decryption failed), length of plain text + * data if everything was fine. + */ +int32_t decrypt_data(const uint8_t *public_key, const uint8_t *secret_key, const uint8_t *nonce, + const uint8_t *encrypted, size_t length, uint8_t *plain); + +/** + * Fast encrypt/decrypt operations. Use if this is not a one-time communication. + * encrypt_precompute does the shared-key generation once so it does not have + * to be preformed on every encrypt/decrypt. + */ +int32_t encrypt_precompute(const uint8_t *public_key, const uint8_t *secret_key, uint8_t *shared_key); + +/** + * Encrypts plain of length length to encrypted of length + CRYPTO_MAC_SIZE + * using a shared key CRYPTO_SYMMETRIC_KEY_SIZE big and a CRYPTO_NONCE_SIZE + * byte nonce. + * + * @return -1 if there was a problem, length of encrypted data if everything + * was fine. + */ +int32_t encrypt_data_symmetric(const uint8_t *shared_key, const uint8_t *nonce, const uint8_t *plain, size_t length, + uint8_t *encrypted); + +/** + * Decrypts encrypted of length length to plain of length length - + * CRYPTO_MAC_SIZE using a shared key CRYPTO_SHARED_KEY_SIZE big and a + * CRYPTO_NONCE_SIZE byte nonce. + * + * @return -1 if there was a problem (decryption failed), length of plain data + * if everything was fine. + */ +int32_t decrypt_data_symmetric(const uint8_t *shared_key, const uint8_t *nonce, const uint8_t *encrypted, size_t length, + uint8_t *plain); + +/** + * Increment the given nonce by 1 in big endian (rightmost byte incremented + * first). + */ +void increment_nonce(uint8_t *nonce); + +/** + * Increment the given nonce by a given number. The number should be in host + * byte order. + */ +void increment_nonce_number(uint8_t *nonce, uint32_t host_order_num); + +/** + * Fill the given nonce with random bytes. + */ +void random_nonce(uint8_t *nonce); + +/** + * Fill a key CRYPTO_SYMMETRIC_KEY_SIZE big with random bytes. + */ +void new_symmetric_key(uint8_t *key); + +/** + * Fill an array of bytes with random values. + */ +void random_bytes(uint8_t *bytes, size_t length); + +#endif /* CRYPTO_CORE_H */ diff --git a/libs/libtox/src/toxcore/crypto_core_mem.c b/libs/libtox/src/toxcore/crypto_core_mem.c new file mode 100644 index 0000000000..b8f4223e65 --- /dev/null +++ b/libs/libtox/src/toxcore/crypto_core_mem.c @@ -0,0 +1,87 @@ +/* + * ISC License + * + * Copyright (c) 2013-2016 + * Frank Denis <j at pureftpd dot org> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "crypto_core.h" + +#ifndef VANILLA_NACL +/* We use libsodium by default. */ +#include <sodium.h> +#else +#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) +#include <windows.h> +#include <wincrypt.h> +#endif +#endif + + +void crypto_memzero(void *data, size_t length) +{ +#ifndef VANILLA_NACL + sodium_memzero(data, length); +#else +#ifdef _WIN32 + SecureZeroMemory(data, length); +#elif defined(HAVE_MEMSET_S) + + if (length > 0U) { + errno_t code = memset_s(data, (rsize_t) length, 0, (rsize_t) length); + + if (code != 0) { + abort(); /* LCOV_EXCL_LINE */ + } + } + +#elif defined(HAVE_EXPLICIT_BZERO) + explicit_bzero(data, length); +#else + volatile unsigned char *volatile pnt = + (volatile unsigned char *volatile) data; + size_t i = (size_t) 0U; + + while (i < length) { + pnt[i++] = 0U; + } + +#endif +#endif +} + +int32_t crypto_memcmp(const void *p1, const void *p2, size_t length) +{ +#ifndef VANILLA_NACL + return sodium_memcmp(p1, p2, length); +#else + const volatile unsigned char *volatile b1 = + (const volatile unsigned char *volatile) p1; + const volatile unsigned char *volatile b2 = + (const volatile unsigned char *volatile) p2; + + size_t i; + unsigned char d = (unsigned char) 0U; + + for (i = 0U; i < length; i++) { + d |= b1[i] ^ b2[i]; + } + + return (1 & ((d - 1) >> 8)) - 1; +#endif +} diff --git a/libs/libtox/src/toxcore/friend_connection.c b/libs/libtox/src/toxcore/friend_connection.c new file mode 100644 index 0000000000..9f0677b109 --- /dev/null +++ b/libs/libtox/src/toxcore/friend_connection.c @@ -0,0 +1,937 @@ +/* + * Connection to friends. + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2014 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "friend_connection.h" + +#include "util.h" + +#define PORTS_PER_DISCOVERY 10 + +/* return 1 if the friendcon_id is not valid. + * return 0 if the friendcon_id is valid. + */ +static uint8_t friendconn_id_not_valid(const Friend_Connections *fr_c, int friendcon_id) +{ + if ((unsigned int)friendcon_id >= fr_c->num_cons) { + return 1; + } + + if (fr_c->conns == NULL) { + return 1; + } + + if (fr_c->conns[friendcon_id].status == FRIENDCONN_STATUS_NONE) { + return 1; + } + + return 0; +} + + +/* Set the size of the friend connections list to num. + * + * return -1 if realloc fails. + * return 0 if it succeeds. + */ +static int realloc_friendconns(Friend_Connections *fr_c, uint32_t num) +{ + if (num == 0) { + free(fr_c->conns); + fr_c->conns = NULL; + return 0; + } + + Friend_Conn *newgroup_cons = (Friend_Conn *)realloc(fr_c->conns, num * sizeof(Friend_Conn)); + + if (newgroup_cons == NULL) { + return -1; + } + + fr_c->conns = newgroup_cons; + return 0; +} + +/* Create a new empty friend connection. + * + * return -1 on failure. + * return friendcon_id on success. + */ +static int create_friend_conn(Friend_Connections *fr_c) +{ + uint32_t i; + + for (i = 0; i < fr_c->num_cons; ++i) { + if (fr_c->conns[i].status == FRIENDCONN_STATUS_NONE) { + return i; + } + } + + int id = -1; + + if (realloc_friendconns(fr_c, fr_c->num_cons + 1) == 0) { + id = fr_c->num_cons; + ++fr_c->num_cons; + memset(&(fr_c->conns[id]), 0, sizeof(Friend_Conn)); + } + + return id; +} + +/* Wipe a friend connection. + * + * return -1 on failure. + * return 0 on success. + */ +static int wipe_friend_conn(Friend_Connections *fr_c, int friendcon_id) +{ + if (friendconn_id_not_valid(fr_c, friendcon_id)) { + return -1; + } + + uint32_t i; + memset(&(fr_c->conns[friendcon_id]), 0 , sizeof(Friend_Conn)); + + for (i = fr_c->num_cons; i != 0; --i) { + if (fr_c->conns[i - 1].status != FRIENDCONN_STATUS_NONE) { + break; + } + } + + if (fr_c->num_cons != i) { + fr_c->num_cons = i; + realloc_friendconns(fr_c, fr_c->num_cons); + } + + return 0; +} + +static Friend_Conn *get_conn(const Friend_Connections *fr_c, int friendcon_id) +{ + if (friendconn_id_not_valid(fr_c, friendcon_id)) { + return 0; + } + + return &fr_c->conns[friendcon_id]; +} + +/* return friendcon_id corresponding to the real public key on success. + * return -1 on failure. + */ +int getfriend_conn_id_pk(Friend_Connections *fr_c, const uint8_t *real_pk) +{ + uint32_t i; + + for (i = 0; i < fr_c->num_cons; ++i) { + Friend_Conn *friend_con = get_conn(fr_c, i); + + if (friend_con) { + if (public_key_cmp(friend_con->real_public_key, real_pk) == 0) { + return i; + } + } + } + + return -1; +} + +/* Add a TCP relay associated to the friend. + * + * return -1 on failure. + * return 0 on success. + */ +int friend_add_tcp_relay(Friend_Connections *fr_c, int friendcon_id, IP_Port ip_port, const uint8_t *public_key) +{ + Friend_Conn *friend_con = get_conn(fr_c, friendcon_id); + + if (!friend_con) { + return -1; + } + + /* Local ip and same pk means that they are hosting a TCP relay. */ + if (Local_ip(ip_port.ip) && public_key_cmp(friend_con->dht_temp_pk, public_key) == 0) { + if (friend_con->dht_ip_port.ip.family != 0) { + ip_port.ip = friend_con->dht_ip_port.ip; + } else { + friend_con->hosting_tcp_relay = 0; + } + } + + unsigned int i; + + uint16_t index = friend_con->tcp_relay_counter % FRIEND_MAX_STORED_TCP_RELAYS; + + for (i = 0; i < FRIEND_MAX_STORED_TCP_RELAYS; ++i) { + if (friend_con->tcp_relays[i].ip_port.ip.family != 0 + && public_key_cmp(friend_con->tcp_relays[i].public_key, public_key) == 0) { + memset(&friend_con->tcp_relays[i], 0, sizeof(Node_format)); + } + } + + friend_con->tcp_relays[index].ip_port = ip_port; + memcpy(friend_con->tcp_relays[index].public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); + ++friend_con->tcp_relay_counter; + + return add_tcp_relay_peer(fr_c->net_crypto, friend_con->crypt_connection_id, ip_port, public_key); +} + +/* Connect to number saved relays for friend. */ +static void connect_to_saved_tcp_relays(Friend_Connections *fr_c, int friendcon_id, unsigned int number) +{ + Friend_Conn *friend_con = get_conn(fr_c, friendcon_id); + + if (!friend_con) { + return; + } + + unsigned int i; + + for (i = 0; (i < FRIEND_MAX_STORED_TCP_RELAYS) && (number != 0); ++i) { + uint16_t index = (friend_con->tcp_relay_counter - (i + 1)) % FRIEND_MAX_STORED_TCP_RELAYS; + + if (friend_con->tcp_relays[index].ip_port.ip.family) { + if (add_tcp_relay_peer(fr_c->net_crypto, friend_con->crypt_connection_id, friend_con->tcp_relays[index].ip_port, + friend_con->tcp_relays[index].public_key) == 0) { + --number; + } + } + } +} + +static unsigned int send_relays(Friend_Connections *fr_c, int friendcon_id) +{ + Friend_Conn *friend_con = get_conn(fr_c, friendcon_id); + + if (!friend_con) { + return 0; + } + + Node_format nodes[MAX_SHARED_RELAYS]; + uint8_t data[1024]; + int n, length; + + n = copy_connected_tcp_relays(fr_c->net_crypto, nodes, MAX_SHARED_RELAYS); + + int i; + + for (i = 0; i < n; ++i) { + /* Associated the relays being sent with this connection. + On receiving the peer will do the same which will establish the connection. */ + friend_add_tcp_relay(fr_c, friendcon_id, nodes[i].ip_port, nodes[i].public_key); + } + + length = pack_nodes(data + 1, sizeof(data) - 1, nodes, n); + + if (length <= 0) { + return 0; + } + + data[0] = PACKET_ID_SHARE_RELAYS; + ++length; + + if (write_cryptpacket(fr_c->net_crypto, friend_con->crypt_connection_id, data, length, 0) != -1) { + friend_con->share_relays_lastsent = unix_time(); + return 1; + } + + return 0; +} + +/* callback for recv TCP relay nodes. */ +static int tcp_relay_node_callback(void *object, uint32_t number, IP_Port ip_port, const uint8_t *public_key) +{ + Friend_Connections *fr_c = (Friend_Connections *)object; + Friend_Conn *friend_con = get_conn(fr_c, number); + + if (!friend_con) { + return -1; + } + + if (friend_con->crypt_connection_id != -1) { + return friend_add_tcp_relay(fr_c, number, ip_port, public_key); + } + + return add_tcp_relay(fr_c->net_crypto, ip_port, public_key); +} + +static int friend_new_connection(Friend_Connections *fr_c, int friendcon_id); +/* Callback for DHT ip_port changes. */ +static void dht_ip_callback(void *object, int32_t number, IP_Port ip_port) +{ + Friend_Connections *fr_c = (Friend_Connections *)object; + Friend_Conn *friend_con = get_conn(fr_c, number); + + if (!friend_con) { + return; + } + + if (friend_con->crypt_connection_id == -1) { + friend_new_connection(fr_c, number); + } + + set_direct_ip_port(fr_c->net_crypto, friend_con->crypt_connection_id, ip_port, 1); + friend_con->dht_ip_port = ip_port; + friend_con->dht_ip_port_lastrecv = unix_time(); + + if (friend_con->hosting_tcp_relay) { + friend_add_tcp_relay(fr_c, number, ip_port, friend_con->dht_temp_pk); + friend_con->hosting_tcp_relay = 0; + } +} + +static void change_dht_pk(Friend_Connections *fr_c, int friendcon_id, const uint8_t *dht_public_key) +{ + Friend_Conn *friend_con = get_conn(fr_c, friendcon_id); + + if (!friend_con) { + return; + } + + friend_con->dht_pk_lastrecv = unix_time(); + + if (friend_con->dht_lock) { + if (DHT_delfriend(fr_c->dht, friend_con->dht_temp_pk, friend_con->dht_lock) != 0) { + printf("a. Could not delete dht peer. Please report this.\n"); + return; + } + + friend_con->dht_lock = 0; + } + + DHT_addfriend(fr_c->dht, dht_public_key, dht_ip_callback, fr_c, friendcon_id, &friend_con->dht_lock); + memcpy(friend_con->dht_temp_pk, dht_public_key, CRYPTO_PUBLIC_KEY_SIZE); +} + +static int handle_status(void *object, int number, uint8_t status, void *userdata) +{ + Friend_Connections *fr_c = (Friend_Connections *)object; + Friend_Conn *friend_con = get_conn(fr_c, number); + + if (!friend_con) { + return -1; + } + + bool call_cb = 0; + + if (status) { /* Went online. */ + call_cb = 1; + friend_con->status = FRIENDCONN_STATUS_CONNECTED; + friend_con->ping_lastrecv = unix_time(); + friend_con->share_relays_lastsent = 0; + onion_set_friend_online(fr_c->onion_c, friend_con->onion_friendnum, status); + } else { /* Went offline. */ + if (friend_con->status != FRIENDCONN_STATUS_CONNECTING) { + call_cb = 1; + friend_con->dht_pk_lastrecv = unix_time(); + onion_set_friend_online(fr_c->onion_c, friend_con->onion_friendnum, status); + } + + friend_con->status = FRIENDCONN_STATUS_CONNECTING; + friend_con->crypt_connection_id = -1; + friend_con->hosting_tcp_relay = 0; + } + + if (call_cb) { + unsigned int i; + + for (i = 0; i < MAX_FRIEND_CONNECTION_CALLBACKS; ++i) { + if (friend_con->callbacks[i].status_callback) { + friend_con->callbacks[i].status_callback( + friend_con->callbacks[i].callback_object, + friend_con->callbacks[i].callback_id, status, userdata); + } + } + } + + return 0; +} + +/* Callback for dht public key changes. */ +static void dht_pk_callback(void *object, int32_t number, const uint8_t *dht_public_key, void *userdata) +{ + Friend_Connections *fr_c = (Friend_Connections *)object; + Friend_Conn *friend_con = get_conn(fr_c, number); + + if (!friend_con) { + return; + } + + if (public_key_cmp(friend_con->dht_temp_pk, dht_public_key) == 0) { + return; + } + + change_dht_pk(fr_c, number, dht_public_key); + + /* if pk changed, create a new connection.*/ + if (friend_con->crypt_connection_id != -1) { + crypto_kill(fr_c->net_crypto, friend_con->crypt_connection_id); + friend_con->crypt_connection_id = -1; + handle_status(object, number, 0, userdata); /* Going offline. */ + } + + friend_new_connection(fr_c, number); + onion_set_friend_DHT_pubkey(fr_c->onion_c, friend_con->onion_friendnum, dht_public_key); +} + +static int handle_packet(void *object, int number, const uint8_t *data, uint16_t length, void *userdata) +{ + if (length == 0) { + return -1; + } + + Friend_Connections *fr_c = (Friend_Connections *)object; + Friend_Conn *friend_con = get_conn(fr_c, number); + + if (!friend_con) { + return -1; + } + + if (data[0] == PACKET_ID_FRIEND_REQUESTS) { + if (fr_c->fr_request_callback) { + fr_c->fr_request_callback(fr_c->fr_request_object, friend_con->real_public_key, data, length, userdata); + } + + return 0; + } + + if (data[0] == PACKET_ID_ALIVE) { + friend_con->ping_lastrecv = unix_time(); + return 0; + } + + if (data[0] == PACKET_ID_SHARE_RELAYS) { + Node_format nodes[MAX_SHARED_RELAYS]; + int n; + + if ((n = unpack_nodes(nodes, MAX_SHARED_RELAYS, NULL, data + 1, length - 1, 1)) == -1) { + return -1; + } + + int j; + + for (j = 0; j < n; j++) { + friend_add_tcp_relay(fr_c, number, nodes[j].ip_port, nodes[j].public_key); + } + + return 0; + } + + unsigned int i; + + for (i = 0; i < MAX_FRIEND_CONNECTION_CALLBACKS; ++i) { + if (friend_con->callbacks[i].data_callback) { + friend_con->callbacks[i].data_callback( + friend_con->callbacks[i].callback_object, + friend_con->callbacks[i].callback_id, data, length, userdata); + } + + friend_con = get_conn(fr_c, number); + + if (!friend_con) { + return -1; + } + } + + return 0; +} + +static int handle_lossy_packet(void *object, int number, const uint8_t *data, uint16_t length, void *userdata) +{ + if (length == 0) { + return -1; + } + + Friend_Connections *fr_c = (Friend_Connections *)object; + Friend_Conn *friend_con = get_conn(fr_c, number); + + if (!friend_con) { + return -1; + } + + unsigned int i; + + for (i = 0; i < MAX_FRIEND_CONNECTION_CALLBACKS; ++i) { + if (friend_con->callbacks[i].lossy_data_callback) { + friend_con->callbacks[i].lossy_data_callback( + friend_con->callbacks[i].callback_object, + friend_con->callbacks[i].callback_id, data, length, userdata); + } + + friend_con = get_conn(fr_c, number); + + if (!friend_con) { + return -1; + } + } + + return 0; +} + +static int handle_new_connections(void *object, New_Connection *n_c) +{ + Friend_Connections *fr_c = (Friend_Connections *)object; + int friendcon_id = getfriend_conn_id_pk(fr_c, n_c->public_key); + Friend_Conn *friend_con = get_conn(fr_c, friendcon_id); + + if (friend_con) { + + if (friend_con->crypt_connection_id != -1) { + return -1; + } + + int id = accept_crypto_connection(fr_c->net_crypto, n_c); + + if (id == -1) { + return -1; + } + + connection_status_handler(fr_c->net_crypto, id, &handle_status, fr_c, friendcon_id); + connection_data_handler(fr_c->net_crypto, id, &handle_packet, fr_c, friendcon_id); + connection_lossy_data_handler(fr_c->net_crypto, id, &handle_lossy_packet, fr_c, friendcon_id); + friend_con->crypt_connection_id = id; + + if (n_c->source.ip.family != TOX_AF_INET && n_c->source.ip.family != TOX_AF_INET6) { + set_direct_ip_port(fr_c->net_crypto, friend_con->crypt_connection_id, friend_con->dht_ip_port, 0); + } else { + friend_con->dht_ip_port = n_c->source; + friend_con->dht_ip_port_lastrecv = unix_time(); + } + + if (public_key_cmp(friend_con->dht_temp_pk, n_c->dht_public_key) != 0) { + change_dht_pk(fr_c, friendcon_id, n_c->dht_public_key); + } + + nc_dht_pk_callback(fr_c->net_crypto, id, &dht_pk_callback, fr_c, friendcon_id); + return 0; + } + + return -1; +} + +static int friend_new_connection(Friend_Connections *fr_c, int friendcon_id) +{ + Friend_Conn *friend_con = get_conn(fr_c, friendcon_id); + + if (!friend_con) { + return -1; + } + + if (friend_con->crypt_connection_id != -1) { + return -1; + } + + /* If dht_temp_pk does not contains a pk. */ + if (!friend_con->dht_lock) { + return -1; + } + + int id = new_crypto_connection(fr_c->net_crypto, friend_con->real_public_key, friend_con->dht_temp_pk); + + if (id == -1) { + return -1; + } + + friend_con->crypt_connection_id = id; + connection_status_handler(fr_c->net_crypto, id, &handle_status, fr_c, friendcon_id); + connection_data_handler(fr_c->net_crypto, id, &handle_packet, fr_c, friendcon_id); + connection_lossy_data_handler(fr_c->net_crypto, id, &handle_lossy_packet, fr_c, friendcon_id); + nc_dht_pk_callback(fr_c->net_crypto, id, &dht_pk_callback, fr_c, friendcon_id); + + return 0; +} + +static int send_ping(const Friend_Connections *fr_c, int friendcon_id) +{ + Friend_Conn *friend_con = get_conn(fr_c, friendcon_id); + + if (!friend_con) { + return -1; + } + + uint8_t ping = PACKET_ID_ALIVE; + int64_t ret = write_cryptpacket(fr_c->net_crypto, friend_con->crypt_connection_id, &ping, sizeof(ping), 0); + + if (ret != -1) { + friend_con->ping_lastsent = unix_time(); + return 0; + } + + return -1; +} + +/* Increases lock_count for the connection with friendcon_id by 1. + * + * return 0 on success. + * return -1 on failure. + */ +int friend_connection_lock(Friend_Connections *fr_c, int friendcon_id) +{ + Friend_Conn *friend_con = get_conn(fr_c, friendcon_id); + + if (!friend_con) { + return -1; + } + + ++friend_con->lock_count; + return 0; +} + +/* return FRIENDCONN_STATUS_CONNECTED if the friend is connected. + * return FRIENDCONN_STATUS_CONNECTING if the friend isn't connected. + * return FRIENDCONN_STATUS_NONE on failure. + */ +unsigned int friend_con_connected(Friend_Connections *fr_c, int friendcon_id) +{ + Friend_Conn *friend_con = get_conn(fr_c, friendcon_id); + + if (!friend_con) { + return 0; + } + + return friend_con->status; +} + +/* Copy public keys associated to friendcon_id. + * + * return 0 on success. + * return -1 on failure. + */ +int get_friendcon_public_keys(uint8_t *real_pk, uint8_t *dht_temp_pk, Friend_Connections *fr_c, int friendcon_id) +{ + Friend_Conn *friend_con = get_conn(fr_c, friendcon_id); + + if (!friend_con) { + return -1; + } + + if (real_pk) { + memcpy(real_pk, friend_con->real_public_key, CRYPTO_PUBLIC_KEY_SIZE); + } + + if (dht_temp_pk) { + memcpy(dht_temp_pk, friend_con->dht_temp_pk, CRYPTO_PUBLIC_KEY_SIZE); + } + + return 0; +} + +/* Set temp dht key for connection. + */ +void set_dht_temp_pk(Friend_Connections *fr_c, int friendcon_id, const uint8_t *dht_temp_pk, void *userdata) +{ + dht_pk_callback(fr_c, friendcon_id, dht_temp_pk, userdata); +} + +/* Set the callbacks for the friend connection. + * index is the index (0 to (MAX_FRIEND_CONNECTION_CALLBACKS - 1)) we want the callback to set in the array. + * + * return 0 on success. + * return -1 on failure + */ +int friend_connection_callbacks(Friend_Connections *fr_c, int friendcon_id, unsigned int index, + int (*status_callback)(void *object, int id, uint8_t status, void *userdata), + int (*data_callback)(void *object, int id, const uint8_t *data, uint16_t len, void *userdata), + int (*lossy_data_callback)(void *object, int id, const uint8_t *data, uint16_t length, void *userdata), + void *object, int number) +{ + Friend_Conn *friend_con = get_conn(fr_c, friendcon_id); + + if (!friend_con) { + return -1; + } + + if (index >= MAX_FRIEND_CONNECTION_CALLBACKS) { + return -1; + } + + friend_con->callbacks[index].status_callback = status_callback; + friend_con->callbacks[index].data_callback = data_callback; + friend_con->callbacks[index].lossy_data_callback = lossy_data_callback; + + friend_con->callbacks[index].callback_object = object; + friend_con->callbacks[index].callback_id = number; + + return 0; +} + +/* return the crypt_connection_id for the connection. + * + * return crypt_connection_id on success. + * return -1 on failure. + */ +int friend_connection_crypt_connection_id(Friend_Connections *fr_c, int friendcon_id) +{ + Friend_Conn *friend_con = get_conn(fr_c, friendcon_id); + + if (!friend_con) { + return -1; + } + + return friend_con->crypt_connection_id; +} + +/* Create a new friend connection. + * If one to that real public key already exists, increase lock count and return it. + * + * return -1 on failure. + * return connection id on success. + */ +int new_friend_connection(Friend_Connections *fr_c, const uint8_t *real_public_key) +{ + int friendcon_id = getfriend_conn_id_pk(fr_c, real_public_key); + + if (friendcon_id != -1) { + ++fr_c->conns[friendcon_id].lock_count; + return friendcon_id; + } + + friendcon_id = create_friend_conn(fr_c); + + if (friendcon_id == -1) { + return -1; + } + + int32_t onion_friendnum = onion_addfriend(fr_c->onion_c, real_public_key); + + if (onion_friendnum == -1) { + return -1; + } + + Friend_Conn *friend_con = &fr_c->conns[friendcon_id]; + + friend_con->crypt_connection_id = -1; + friend_con->status = FRIENDCONN_STATUS_CONNECTING; + memcpy(friend_con->real_public_key, real_public_key, CRYPTO_PUBLIC_KEY_SIZE); + friend_con->onion_friendnum = onion_friendnum; + + recv_tcp_relay_handler(fr_c->onion_c, onion_friendnum, &tcp_relay_node_callback, fr_c, friendcon_id); + onion_dht_pk_callback(fr_c->onion_c, onion_friendnum, &dht_pk_callback, fr_c, friendcon_id); + + return friendcon_id; +} + +/* Kill a friend connection. + * + * return -1 on failure. + * return 0 on success. + */ +int kill_friend_connection(Friend_Connections *fr_c, int friendcon_id) +{ + Friend_Conn *friend_con = get_conn(fr_c, friendcon_id); + + if (!friend_con) { + return -1; + } + + if (friend_con->lock_count) { + --friend_con->lock_count; + return 0; + } + + onion_delfriend(fr_c->onion_c, friend_con->onion_friendnum); + crypto_kill(fr_c->net_crypto, friend_con->crypt_connection_id); + + if (friend_con->dht_lock) { + DHT_delfriend(fr_c->dht, friend_con->dht_temp_pk, friend_con->dht_lock); + } + + return wipe_friend_conn(fr_c, friendcon_id); +} + + +/* Set friend request callback. + * + * This function will be called every time a friend request packet is received. + */ +void set_friend_request_callback(Friend_Connections *fr_c, int (*fr_request_callback)(void *, const uint8_t *, + const uint8_t *, uint16_t, void *), void *object) +{ + fr_c->fr_request_callback = fr_request_callback; + fr_c->fr_request_object = object; + oniondata_registerhandler(fr_c->onion_c, CRYPTO_PACKET_FRIEND_REQ, fr_request_callback, object); +} + +/* Send a Friend request packet. + * + * return -1 if failure. + * return 0 if it sent the friend request directly to the friend. + * return the number of peers it was routed through if it did not send it directly. + */ +int send_friend_request_packet(Friend_Connections *fr_c, int friendcon_id, uint32_t nospam_num, const uint8_t *data, + uint16_t length) +{ + if (1 + sizeof(nospam_num) + length > ONION_CLIENT_MAX_DATA_SIZE || length == 0) { + return -1; + } + + Friend_Conn *friend_con = get_conn(fr_c, friendcon_id); + + if (!friend_con) { + return -1; + } + + VLA(uint8_t, packet, 1 + sizeof(nospam_num) + length); + memcpy(packet + 1, &nospam_num, sizeof(nospam_num)); + memcpy(packet + 1 + sizeof(nospam_num), data, length); + + if (friend_con->status == FRIENDCONN_STATUS_CONNECTED) { + packet[0] = PACKET_ID_FRIEND_REQUESTS; + return write_cryptpacket(fr_c->net_crypto, friend_con->crypt_connection_id, packet, SIZEOF_VLA(packet), 0) != -1; + } + + packet[0] = CRYPTO_PACKET_FRIEND_REQ; + int num = send_onion_data(fr_c->onion_c, friend_con->onion_friendnum, packet, SIZEOF_VLA(packet)); + + if (num <= 0) { + return -1; + } + + return num; +} + +/* Create new friend_connections instance. */ +Friend_Connections *new_friend_connections(Onion_Client *onion_c, bool local_discovery_enabled) +{ + if (!onion_c) { + return NULL; + } + + Friend_Connections *temp = (Friend_Connections *)calloc(1, sizeof(Friend_Connections)); + + if (temp == NULL) { + return NULL; + } + + temp->dht = onion_c->dht; + temp->net_crypto = onion_c->c; + temp->onion_c = onion_c; + temp->local_discovery_enabled = local_discovery_enabled; + // Don't include default port in port range + temp->next_LANport = TOX_PORTRANGE_FROM + 1; + + new_connection_handler(temp->net_crypto, &handle_new_connections, temp); + + if (temp->local_discovery_enabled) { + LANdiscovery_init(temp->dht); + } + + return temp; +} + +/* Send a LAN discovery packet every LAN_DISCOVERY_INTERVAL seconds. */ +static void LANdiscovery(Friend_Connections *fr_c) +{ + if (fr_c->last_LANdiscovery + LAN_DISCOVERY_INTERVAL < unix_time()) { + const uint16_t first = fr_c->next_LANport; + uint16_t last = first + PORTS_PER_DISCOVERY; + last = last > TOX_PORTRANGE_TO ? TOX_PORTRANGE_TO : last; + + // Always send to default port + send_LANdiscovery(net_htons(TOX_PORT_DEFAULT), fr_c->dht); + + // And check some extra ports + for (uint16_t port = first; port < last; port++) { + send_LANdiscovery(net_htons(port), fr_c->dht); + } + + // Don't include default port in port range + fr_c->next_LANport = last != TOX_PORTRANGE_TO ? last : TOX_PORTRANGE_FROM + 1; + fr_c->last_LANdiscovery = unix_time(); + } +} + +/* main friend_connections loop. */ +void do_friend_connections(Friend_Connections *fr_c, void *userdata) +{ + uint32_t i; + uint64_t temp_time = unix_time(); + + for (i = 0; i < fr_c->num_cons; ++i) { + Friend_Conn *friend_con = get_conn(fr_c, i); + + if (friend_con) { + if (friend_con->status == FRIENDCONN_STATUS_CONNECTING) { + if (friend_con->dht_pk_lastrecv + FRIEND_DHT_TIMEOUT < temp_time) { + if (friend_con->dht_lock) { + DHT_delfriend(fr_c->dht, friend_con->dht_temp_pk, friend_con->dht_lock); + friend_con->dht_lock = 0; + memset(friend_con->dht_temp_pk, 0, CRYPTO_PUBLIC_KEY_SIZE); + } + } + + if (friend_con->dht_ip_port_lastrecv + FRIEND_DHT_TIMEOUT < temp_time) { + friend_con->dht_ip_port.ip.family = 0; + } + + if (friend_con->dht_lock) { + if (friend_new_connection(fr_c, i) == 0) { + set_direct_ip_port(fr_c->net_crypto, friend_con->crypt_connection_id, friend_con->dht_ip_port, 0); + connect_to_saved_tcp_relays(fr_c, i, (MAX_FRIEND_TCP_CONNECTIONS / 2)); /* Only fill it half up. */ + } + } + } else if (friend_con->status == FRIENDCONN_STATUS_CONNECTED) { + if (friend_con->ping_lastsent + FRIEND_PING_INTERVAL < temp_time) { + send_ping(fr_c, i); + } + + if (friend_con->share_relays_lastsent + SHARE_RELAYS_INTERVAL < temp_time) { + send_relays(fr_c, i); + } + + if (friend_con->ping_lastrecv + FRIEND_CONNECTION_TIMEOUT < temp_time) { + /* If we stopped receiving ping packets, kill it. */ + crypto_kill(fr_c->net_crypto, friend_con->crypt_connection_id); + friend_con->crypt_connection_id = -1; + handle_status(fr_c, i, 0, userdata); /* Going offline. */ + } + } + } + } + + if (fr_c->local_discovery_enabled) { + LANdiscovery(fr_c); + } +} + +/* Free everything related with friend_connections. */ +void kill_friend_connections(Friend_Connections *fr_c) +{ + if (!fr_c) { + return; + } + + uint32_t i; + + for (i = 0; i < fr_c->num_cons; ++i) { + kill_friend_connection(fr_c, i); + } + + if (fr_c->local_discovery_enabled) { + LANdiscovery_kill(fr_c->dht); + } + + free(fr_c); +} diff --git a/libs/libtox/src/toxcore/friend_connection.h b/libs/libtox/src/toxcore/friend_connection.h new file mode 100644 index 0000000000..e993a103d3 --- /dev/null +++ b/libs/libtox/src/toxcore/friend_connection.h @@ -0,0 +1,210 @@ +/* + * Connection to friends. + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2014 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifndef FRIEND_CONNECTION_H +#define FRIEND_CONNECTION_H + +#include "DHT.h" +#include "LAN_discovery.h" +#include "net_crypto.h" +#include "onion_client.h" + +#define MAX_FRIEND_CONNECTION_CALLBACKS 2 +#define MESSENGER_CALLBACK_INDEX 0 +#define GROUPCHAT_CALLBACK_INDEX 1 + +#define PACKET_ID_ALIVE 16 +#define PACKET_ID_SHARE_RELAYS 17 +#define PACKET_ID_FRIEND_REQUESTS 18 + +/* Interval between the sending of ping packets. */ +#define FRIEND_PING_INTERVAL 8 + +/* If no packets are received from friend in this time interval, kill the connection. */ +#define FRIEND_CONNECTION_TIMEOUT (FRIEND_PING_INTERVAL * 4) + +/* Time before friend is removed from the DHT after last hearing about him. */ +#define FRIEND_DHT_TIMEOUT BAD_NODE_TIMEOUT + +#define FRIEND_MAX_STORED_TCP_RELAYS (MAX_FRIEND_TCP_CONNECTIONS * 4) + +/* Max number of tcp relays sent to friends */ +#define MAX_SHARED_RELAYS (RECOMMENDED_FRIEND_TCP_CONNECTIONS) + +/* Interval between the sending of tcp relay information */ +#define SHARE_RELAYS_INTERVAL (5 * 60) + + +enum { + FRIENDCONN_STATUS_NONE, + FRIENDCONN_STATUS_CONNECTING, + FRIENDCONN_STATUS_CONNECTED +}; + +typedef struct { + uint8_t status; + + uint8_t real_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t dht_temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; + uint16_t dht_lock; + IP_Port dht_ip_port; + uint64_t dht_pk_lastrecv, dht_ip_port_lastrecv; + + int onion_friendnum; + int crypt_connection_id; + + uint64_t ping_lastrecv, ping_lastsent; + uint64_t share_relays_lastsent; + + struct { + int (*status_callback)(void *object, int id, uint8_t status, void *userdata); + int (*data_callback)(void *object, int id, const uint8_t *data, uint16_t length, void *userdata); + int (*lossy_data_callback)(void *object, int id, const uint8_t *data, uint16_t length, void *userdata); + + void *callback_object; + int callback_id; + } callbacks[MAX_FRIEND_CONNECTION_CALLBACKS]; + + uint16_t lock_count; + + Node_format tcp_relays[FRIEND_MAX_STORED_TCP_RELAYS]; + uint16_t tcp_relay_counter; + + bool hosting_tcp_relay; +} Friend_Conn; + + +typedef struct { + Net_Crypto *net_crypto; + DHT *dht; + Onion_Client *onion_c; + + Friend_Conn *conns; + uint32_t num_cons; + + int (*fr_request_callback)(void *object, const uint8_t *source_pubkey, const uint8_t *data, uint16_t len, + void *userdata); + void *fr_request_object; + + uint64_t last_LANdiscovery; + uint16_t next_LANport; + + bool local_discovery_enabled; +} Friend_Connections; + +/* return friendcon_id corresponding to the real public key on success. + * return -1 on failure. + */ +int getfriend_conn_id_pk(Friend_Connections *fr_c, const uint8_t *real_pk); + +/* Increases lock_count for the connection with friendcon_id by 1. + * + * return 0 on success. + * return -1 on failure. + */ +int friend_connection_lock(Friend_Connections *fr_c, int friendcon_id); + +/* return FRIENDCONN_STATUS_CONNECTED if the friend is connected. + * return FRIENDCONN_STATUS_CONNECTING if the friend isn't connected. + * return FRIENDCONN_STATUS_NONE on failure. + */ +unsigned int friend_con_connected(Friend_Connections *fr_c, int friendcon_id); + +/* Copy public keys associated to friendcon_id. + * + * return 0 on success. + * return -1 on failure. + */ +int get_friendcon_public_keys(uint8_t *real_pk, uint8_t *dht_temp_pk, Friend_Connections *fr_c, int friendcon_id); + +/* Set temp dht key for connection. + */ +void set_dht_temp_pk(Friend_Connections *fr_c, int friendcon_id, const uint8_t *dht_temp_pk, void *userdata); + +/* Add a TCP relay associated to the friend. + * + * return -1 on failure. + * return 0 on success. + */ +int friend_add_tcp_relay(Friend_Connections *fr_c, int friendcon_id, IP_Port ip_port, const uint8_t *public_key); + +/* Set the callbacks for the friend connection. + * index is the index (0 to (MAX_FRIEND_CONNECTION_CALLBACKS - 1)) we want the callback to set in the array. + * + * return 0 on success. + * return -1 on failure + */ +int friend_connection_callbacks(Friend_Connections *fr_c, int friendcon_id, unsigned int index, + int (*status_callback)(void *object, int id, uint8_t status, void *userdata), + int (*data_callback)(void *object, int id, const uint8_t *data, uint16_t len, void *userdata), + int (*lossy_data_callback)(void *object, int id, const uint8_t *data, uint16_t length, void *userdata), + void *object, int number); + +/* return the crypt_connection_id for the connection. + * + * return crypt_connection_id on success. + * return -1 on failure. + */ +int friend_connection_crypt_connection_id(Friend_Connections *fr_c, int friendcon_id); + +/* Create a new friend connection. + * If one to that real public key already exists, increase lock count and return it. + * + * return -1 on failure. + * return connection id on success. + */ +int new_friend_connection(Friend_Connections *fr_c, const uint8_t *real_public_key); + +/* Kill a friend connection. + * + * return -1 on failure. + * return 0 on success. + */ +int kill_friend_connection(Friend_Connections *fr_c, int friendcon_id); + +/* Send a Friend request packet. + * + * return -1 if failure. + * return 0 if it sent the friend request directly to the friend. + * return the number of peers it was routed through if it did not send it directly. + */ +int send_friend_request_packet(Friend_Connections *fr_c, int friendcon_id, uint32_t nospam_num, const uint8_t *data, + uint16_t length); + +/* Set friend request callback. + * + * This function will be called every time a friend request is received. + */ +void set_friend_request_callback(Friend_Connections *fr_c, int (*fr_request_callback)(void *, const uint8_t *, + const uint8_t *, uint16_t, void *), void *object); + +/* Create new friend_connections instance. */ +Friend_Connections *new_friend_connections(Onion_Client *onion_c, bool local_discovery_enabled); + +/* main friend_connections loop. */ +void do_friend_connections(Friend_Connections *fr_c, void *userdata); + +/* Free everything related with friend_connections. */ +void kill_friend_connections(Friend_Connections *fr_c); + +#endif diff --git a/libs/libtox/src/toxcore/friend_requests.c b/libs/libtox/src/toxcore/friend_requests.c new file mode 100644 index 0000000000..ba782e2b01 --- /dev/null +++ b/libs/libtox/src/toxcore/friend_requests.c @@ -0,0 +1,152 @@ +/* + * Handle friend requests. + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2013 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "friend_requests.h" + +#include "util.h" + +/* Set and get the nospam variable used to prevent one type of friend request spam. */ +void set_nospam(Friend_Requests *fr, uint32_t num) +{ + fr->nospam = num; +} + +uint32_t get_nospam(const Friend_Requests *fr) +{ + return fr->nospam; +} + + +/* Set the function that will be executed when a friend request is received. */ +void callback_friendrequest(Friend_Requests *fr, void (*function)(void *, const uint8_t *, const uint8_t *, size_t, + void *), void *object) +{ + fr->handle_friendrequest = function; + fr->handle_friendrequest_isset = 1; + fr->handle_friendrequest_object = object; +} +/* 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)(const 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, const uint8_t *real_pk) +{ + if (fr->received_requests_index >= MAX_RECEIVED_STORED) { + fr->received_requests_index = 0; + } + + id_copy(fr->received_requests[fr->received_requests_index], real_pk); + ++fr->received_requests_index; +} + +/* Check if a friend request was already received. + * + * return 0 if it did not. + * return 1 if it did. + */ +static int request_received(Friend_Requests *fr, const uint8_t *real_pk) +{ + uint32_t i; + + for (i = 0; i < MAX_RECEIVED_STORED; ++i) { + if (id_equal(fr->received_requests[i], real_pk)) { + return 1; + } + } + + return 0; +} + +/* Remove real pk from received_requests list. + * + * return 0 if it removed it successfully. + * return -1 if it didn't find it. + */ +int remove_request_received(Friend_Requests *fr, const uint8_t *real_pk) +{ + uint32_t i; + + for (i = 0; i < MAX_RECEIVED_STORED; ++i) { + if (id_equal(fr->received_requests[i], real_pk)) { + crypto_memzero(fr->received_requests[i], CRYPTO_PUBLIC_KEY_SIZE); + return 0; + } + } + + return -1; +} + + +static int friendreq_handlepacket(void *object, const uint8_t *source_pubkey, const uint8_t *packet, uint16_t length, + void *userdata) +{ + Friend_Requests *fr = (Friend_Requests *)object; + + if (length <= 1 + sizeof(fr->nospam) || length > ONION_CLIENT_MAX_DATA_SIZE) { + return 1; + } + + ++packet; + --length; + + if (fr->handle_friendrequest_isset == 0) { + return 1; + } + + if (request_received(fr, source_pubkey)) { + return 1; + } + + 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); + + uint32_t message_len = length - sizeof(fr->nospam); + VLA(uint8_t, message, message_len + 1); + memcpy(message, packet + sizeof(fr->nospam), message_len); + message[SIZEOF_VLA(message) - 1] = 0; /* Be sure the message is null terminated. */ + + (*fr->handle_friendrequest)(fr->handle_friendrequest_object, source_pubkey, message, message_len, userdata); + return 0; +} + +void friendreq_init(Friend_Requests *fr, Friend_Connections *fr_c) +{ + set_friend_request_callback(fr_c, &friendreq_handlepacket, fr); +} diff --git a/libs/libtox/src/toxcore/friend_requests.h b/libs/libtox/src/toxcore/friend_requests.h new file mode 100644 index 0000000000..316e1c671f --- /dev/null +++ b/libs/libtox/src/toxcore/friend_requests.h @@ -0,0 +1,76 @@ +/* + * Handle friend requests. + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2014 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifndef FRIEND_REQUESTS_H +#define FRIEND_REQUESTS_H + +#include "friend_connection.h" + +#define MAX_FRIEND_REQUEST_DATA_SIZE (ONION_CLIENT_MAX_DATA_SIZE - (1 + sizeof(uint32_t))) + +typedef struct { + uint32_t nospam; + void (*handle_friendrequest)(void *, const uint8_t *, const uint8_t *, size_t, void *); + uint8_t handle_friendrequest_isset; + void *handle_friendrequest_object; + + int (*filter_function)(const 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(irungentoo): Make this better (This will most likely tie in with the way we will handle spam.) + */ + +#define MAX_RECEIVED_STORED 32 + + uint8_t received_requests[MAX_RECEIVED_STORED][CRYPTO_PUBLIC_KEY_SIZE]; + uint16_t received_requests_index; +} Friend_Requests; + +/* Set and get the nospam variable used to prevent one type of friend request spam. */ +void set_nospam(Friend_Requests *fr, uint32_t num); +uint32_t get_nospam(const Friend_Requests *fr); + +/* Remove real_pk from received_requests list. + * + * return 0 if it removed it successfully. + * return -1 if it didn't find it. + */ +int remove_request_received(Friend_Requests *fr, const uint8_t *real_pk); + +/* 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, size_t length, void * userdata) + */ +void callback_friendrequest(Friend_Requests *fr, void (*function)(void *, const uint8_t *, const uint8_t *, size_t, + void *), void *object); + +/* 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)(const uint8_t *, void *), void *userdata); + +/* Sets up friendreq packet handlers. */ +void friendreq_init(Friend_Requests *fr, Friend_Connections *fr_c); + + +#endif diff --git a/libs/libtox/src/toxcore/group.c b/libs/libtox/src/toxcore/group.c new file mode 100644 index 0000000000..d3f068dfce --- /dev/null +++ b/libs/libtox/src/toxcore/group.c @@ -0,0 +1,2544 @@ +/* + * Slightly better groupchats implementation. + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2014 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "group.h" + +#include "util.h" + +/* return 1 if the groupnumber is not valid. + * return 0 if the groupnumber is valid. + */ +static uint8_t groupnumber_not_valid(const Group_Chats *g_c, int groupnumber) +{ + if ((unsigned int)groupnumber >= g_c->num_chats) { + return 1; + } + + if (g_c->chats == NULL) { + return 1; + } + + if (g_c->chats[groupnumber].status == GROUPCHAT_STATUS_NONE) { + return 1; + } + + return 0; +} + + +/* Set the size of the groupchat list to num. + * + * return -1 if realloc fails. + * return 0 if it succeeds. + */ +static int realloc_groupchats(Group_Chats *g_c, uint32_t num) +{ + if (num == 0) { + free(g_c->chats); + g_c->chats = NULL; + return 0; + } + + Group_c *newgroup_chats = (Group_c *)realloc(g_c->chats, num * sizeof(Group_c)); + + if (newgroup_chats == NULL) { + return -1; + } + + g_c->chats = newgroup_chats; + return 0; +} + + +/* Create a new empty groupchat connection. + * + * return -1 on failure. + * return groupnumber on success. + */ +static int create_group_chat(Group_Chats *g_c) +{ + uint32_t i; + + for (i = 0; i < g_c->num_chats; ++i) { + if (g_c->chats[i].status == GROUPCHAT_STATUS_NONE) { + return i; + } + } + + int id = -1; + + if (realloc_groupchats(g_c, g_c->num_chats + 1) == 0) { + id = g_c->num_chats; + ++g_c->num_chats; + memset(&(g_c->chats[id]), 0, sizeof(Group_c)); + } + + return id; +} + + +/* Wipe a groupchat. + * + * return -1 on failure. + * return 0 on success. + */ +static int wipe_group_chat(Group_Chats *g_c, int groupnumber) +{ + if (groupnumber_not_valid(g_c, groupnumber)) { + return -1; + } + + uint32_t i; + crypto_memzero(&(g_c->chats[groupnumber]), sizeof(Group_c)); + + for (i = g_c->num_chats; i != 0; --i) { + if (g_c->chats[i - 1].status != GROUPCHAT_STATUS_NONE) { + break; + } + } + + if (g_c->num_chats != i) { + g_c->num_chats = i; + realloc_groupchats(g_c, g_c->num_chats); + } + + return 0; +} + +static Group_c *get_group_c(const Group_Chats *g_c, int groupnumber) +{ + if (groupnumber_not_valid(g_c, groupnumber)) { + return 0; + } + + return &g_c->chats[groupnumber]; +} + +/* + * check if peer with real_pk is in peer array. + * + * return peer index if peer is in chat. + * return -1 if peer is not in chat. + * + * TODO(irungentoo): make this more efficient. + */ + +static int peer_in_chat(const Group_c *chat, const uint8_t *real_pk) +{ + uint32_t i; + + for (i = 0; i < chat->numpeers; ++i) { + if (id_equal(chat->group[i].real_pk, real_pk)) { + return i; + } + } + + return -1; +} + +/* + * check if group with identifier is in group array. + * + * return group number if peer is in list. + * return -1 if group is not in list. + * + * TODO(irungentoo): make this more efficient and maybe use constant time comparisons? + */ +static int get_group_num(const Group_Chats *g_c, const uint8_t *identifier) +{ + uint32_t i; + + for (i = 0; i < g_c->num_chats; ++i) { + if (crypto_memcmp(g_c->chats[i].identifier, identifier, GROUP_IDENTIFIER_LENGTH) == 0) { + return i; + } + } + + return -1; +} + +/* + * check if peer with peer_number is in peer array. + * + * return peer number if peer is in chat. + * return -1 if peer is not in chat. + * + * TODO(irungentoo): make this more efficient. + */ +static int get_peer_index(Group_c *g, uint16_t peer_number) +{ + uint32_t i; + + for (i = 0; i < g->numpeers; ++i) { + if (g->group[i].peer_number == peer_number) { + return i; + } + } + + return -1; +} + + +static uint64_t calculate_comp_value(const uint8_t *pk1, const uint8_t *pk2) +{ + uint64_t cmp1 = 0, cmp2 = 0; + + unsigned int i; + + for (i = 0; i < sizeof(uint64_t); ++i) { + cmp1 = (cmp1 << 8) + (uint64_t)pk1[i]; + cmp2 = (cmp2 << 8) + (uint64_t)pk2[i]; + } + + return (cmp1 - cmp2); +} + +enum { + GROUPCHAT_CLOSEST_NONE, + GROUPCHAT_CLOSEST_ADDED, + GROUPCHAT_CLOSEST_REMOVED +}; + +static int friend_in_close(Group_c *g, int friendcon_id); +static int add_conn_to_groupchat(Group_Chats *g_c, int friendcon_id, int groupnumber, uint8_t closest, uint8_t lock); + +static int add_to_closest(Group_Chats *g_c, int groupnumber, const uint8_t *real_pk, const uint8_t *temp_pk) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + if (public_key_cmp(g->real_pk, real_pk) == 0) { + return -1; + } + + unsigned int i; + unsigned int index = DESIRED_CLOSE_CONNECTIONS; + + for (i = 0; i < DESIRED_CLOSE_CONNECTIONS; ++i) { + if (g->closest_peers[i].entry && public_key_cmp(real_pk, g->closest_peers[i].real_pk) == 0) { + return 0; + } + } + + for (i = 0; i < DESIRED_CLOSE_CONNECTIONS; ++i) { + if (g->closest_peers[i].entry == 0) { + index = i; + break; + } + } + + if (index == DESIRED_CLOSE_CONNECTIONS) { + uint64_t comp_val = calculate_comp_value(g->real_pk, real_pk); + uint64_t comp_d = 0; + + for (i = 0; i < (DESIRED_CLOSE_CONNECTIONS / 2); ++i) { + uint64_t comp; + comp = calculate_comp_value(g->real_pk, g->closest_peers[i].real_pk); + + if (comp > comp_val && comp > comp_d) { + index = i; + comp_d = comp; + } + } + + comp_val = calculate_comp_value(real_pk, g->real_pk); + + for (i = (DESIRED_CLOSE_CONNECTIONS / 2); i < DESIRED_CLOSE_CONNECTIONS; ++i) { + uint64_t comp = calculate_comp_value(g->closest_peers[i].real_pk, g->real_pk); + + if (comp > comp_val && comp > comp_d) { + index = i; + comp_d = comp; + } + } + } + + if (index == DESIRED_CLOSE_CONNECTIONS) { + return -1; + } + + uint8_t old_real_pk[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t old_temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t old = 0; + + if (g->closest_peers[index].entry) { + memcpy(old_real_pk, g->closest_peers[index].real_pk, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(old_temp_pk, g->closest_peers[index].temp_pk, CRYPTO_PUBLIC_KEY_SIZE); + old = 1; + } + + g->closest_peers[index].entry = 1; + memcpy(g->closest_peers[index].real_pk, real_pk, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(g->closest_peers[index].temp_pk, temp_pk, CRYPTO_PUBLIC_KEY_SIZE); + + if (old) { + add_to_closest(g_c, groupnumber, old_real_pk, old_temp_pk); + } + + if (!g->changed) { + g->changed = GROUPCHAT_CLOSEST_ADDED; + } + + return 0; +} + +static unsigned int pk_in_closest_peers(Group_c *g, uint8_t *real_pk) +{ + unsigned int i; + + for (i = 0; i < DESIRED_CLOSE_CONNECTIONS; ++i) { + if (!g->closest_peers[i].entry) { + continue; + } + + if (public_key_cmp(g->closest_peers[i].real_pk, real_pk) == 0) { + return 1; + } + } + + return 0; +} + +static int send_packet_online(Friend_Connections *fr_c, int friendcon_id, uint16_t group_num, uint8_t *identifier); + +static int connect_to_closest(Group_Chats *g_c, int groupnumber, void *userdata) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + if (!g->changed) { + return 0; + } + + unsigned int i; + + if (g->changed == GROUPCHAT_CLOSEST_REMOVED) { + for (i = 0; i < g->numpeers; ++i) { + add_to_closest(g_c, groupnumber, g->group[i].real_pk, g->group[i].temp_pk); + } + } + + for (i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { + if (g->close[i].type == GROUPCHAT_CLOSE_NONE) { + continue; + } + + if (!g->close[i].closest) { + continue; + } + + uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t dht_temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; + get_friendcon_public_keys(real_pk, dht_temp_pk, g_c->fr_c, g->close[i].number); + + if (!pk_in_closest_peers(g, real_pk)) { + g->close[i].type = GROUPCHAT_CLOSE_NONE; + kill_friend_connection(g_c->fr_c, g->close[i].number); + } + } + + for (i = 0; i < DESIRED_CLOSE_CONNECTIONS; ++i) { + if (!g->closest_peers[i].entry) { + continue; + } + + int friendcon_id = getfriend_conn_id_pk(g_c->fr_c, g->closest_peers[i].real_pk); + + uint8_t lock = 1; + + if (friendcon_id == -1) { + friendcon_id = new_friend_connection(g_c->fr_c, g->closest_peers[i].real_pk); + lock = 0; + + if (friendcon_id == -1) { + continue; + } + + set_dht_temp_pk(g_c->fr_c, friendcon_id, g->closest_peers[i].temp_pk, userdata); + } + + add_conn_to_groupchat(g_c, friendcon_id, groupnumber, 1, lock); + + if (friend_con_connected(g_c->fr_c, friendcon_id) == FRIENDCONN_STATUS_CONNECTED) { + send_packet_online(g_c->fr_c, friendcon_id, groupnumber, g->identifier); + } + } + + g->changed = GROUPCHAT_CLOSEST_NONE; + + return 0; +} + +/* Add a peer to the group chat. + * + * do_gc_callback indicates whether we want to trigger callbacks set by the client + * via the public API. This should be set to false if this function is called + * from outside of the tox_iterate() loop. + * + * return peer_index if success or peer already in chat. + * return -1 if error. + */ +static int addpeer(Group_Chats *g_c, int groupnumber, const uint8_t *real_pk, const uint8_t *temp_pk, + uint16_t peer_number, void *userdata, bool do_gc_callback) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + // TODO(irungentoo): + int peer_index = peer_in_chat(g, real_pk); + + if (peer_index != -1) { + id_copy(g->group[peer_index].temp_pk, temp_pk); + + if (g->group[peer_index].peer_number != peer_number) { + return -1; + } + + return peer_index; + } + + peer_index = get_peer_index(g, peer_number); + + if (peer_index != -1) { + return -1; + } + + Group_Peer *temp = (Group_Peer *)realloc(g->group, sizeof(Group_Peer) * (g->numpeers + 1)); + + if (temp == NULL) { + return -1; + } + + memset(&(temp[g->numpeers]), 0, sizeof(Group_Peer)); + g->group = temp; + + id_copy(g->group[g->numpeers].real_pk, real_pk); + id_copy(g->group[g->numpeers].temp_pk, temp_pk); + g->group[g->numpeers].peer_number = peer_number; + + g->group[g->numpeers].last_recv = unix_time(); + ++g->numpeers; + + add_to_closest(g_c, groupnumber, real_pk, temp_pk); + + if (do_gc_callback && g_c->group_namelistchange) { + g_c->group_namelistchange(g_c->m, groupnumber, g->numpeers - 1, CHAT_CHANGE_PEER_ADD, userdata); + } + + if (g->peer_on_join) { + g->peer_on_join(g->object, groupnumber, g->numpeers - 1); + } + + return (g->numpeers - 1); +} + +static int remove_close_conn(Group_Chats *g_c, int groupnumber, int friendcon_id) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + uint32_t i; + + for (i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { + if (g->close[i].type == GROUPCHAT_CLOSE_NONE) { + continue; + } + + if (g->close[i].number == (unsigned int)friendcon_id) { + g->close[i].type = GROUPCHAT_CLOSE_NONE; + kill_friend_connection(g_c->fr_c, friendcon_id); + return 0; + } + } + + return -1; +} + + +/* + * Delete a peer from the group chat. + * + * return 0 if success + * return -1 if error. + */ +static int delpeer(Group_Chats *g_c, int groupnumber, int peer_index, void *userdata) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + uint32_t i; + + for (i = 0; i < DESIRED_CLOSE_CONNECTIONS; ++i) { /* If peer is in closest_peers list, remove it. */ + if (g->closest_peers[i].entry && id_equal(g->closest_peers[i].real_pk, g->group[peer_index].real_pk)) { + g->closest_peers[i].entry = 0; + g->changed = GROUPCHAT_CLOSEST_REMOVED; + break; + } + } + + int friendcon_id = getfriend_conn_id_pk(g_c->fr_c, g->group[peer_index].real_pk); + + if (friendcon_id != -1) { + remove_close_conn(g_c, groupnumber, friendcon_id); + } + + --g->numpeers; + + void *peer_object = g->group[peer_index].object; + + if (g->numpeers == 0) { + free(g->group); + g->group = NULL; + } else { + if (g->numpeers != (uint32_t)peer_index) { + memcpy(&g->group[peer_index], &g->group[g->numpeers], sizeof(Group_Peer)); + } + + Group_Peer *temp = (Group_Peer *)realloc(g->group, sizeof(Group_Peer) * (g->numpeers)); + + if (temp == NULL) { + return -1; + } + + g->group = temp; + } + + if (g_c->group_namelistchange) { + g_c->group_namelistchange(g_c->m, groupnumber, peer_index, CHAT_CHANGE_PEER_DEL, userdata); + } + + if (g->peer_on_leave) { + g->peer_on_leave(g->object, groupnumber, peer_index, peer_object); + } + + return 0; +} + +/* Set the nick for a peer. + * + * do_gc_callback indicates whether we want to trigger callbacks set by the client + * via the public API. This should be set to false if this function is called + * from outside of the tox_iterate() loop. + * + * return 0 on success. + * return -1 if error. + */ +static int setnick(Group_Chats *g_c, int groupnumber, int peer_index, const uint8_t *nick, uint16_t nick_len, + void *userdata, bool do_gc_callback) +{ + if (nick_len > MAX_NAME_LENGTH) { + return -1; + } + + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + /* same name as already stored? */ + if (g->group[peer_index].nick_len == nick_len) { + if (nick_len == 0 || !memcmp(g->group[peer_index].nick, nick, nick_len)) { + return 0; + } + } + + if (nick_len) { + memcpy(g->group[peer_index].nick, nick, nick_len); + } + + g->group[peer_index].nick_len = nick_len; + + if (do_gc_callback && g_c->group_namelistchange) { + g_c->group_namelistchange(g_c->m, groupnumber, peer_index, CHAT_CHANGE_PEER_NAME, userdata); + } + + return 0; +} + +static int settitle(Group_Chats *g_c, int groupnumber, int peer_index, const uint8_t *title, uint8_t title_len, + void *userdata) +{ + if (title_len > MAX_NAME_LENGTH || title_len == 0) { + return -1; + } + + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + /* same as already set? */ + if (g->title_len == title_len && !memcmp(g->title, title, title_len)) { + return 0; + } + + memcpy(g->title, title, title_len); + g->title_len = title_len; + + if (g_c->title_callback) { + g_c->title_callback(g_c->m, groupnumber, peer_index, title, title_len, userdata); + } + + return 0; +} + +static void set_conns_type_close(Group_Chats *g_c, int groupnumber, int friendcon_id, uint8_t type) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return; + } + + uint32_t i; + + for (i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { + if (g->close[i].type == GROUPCHAT_CLOSE_NONE) { + continue; + } + + if (g->close[i].number != (unsigned int)friendcon_id) { + continue; + } + + if (type == GROUPCHAT_CLOSE_ONLINE) { + send_packet_online(g_c->fr_c, friendcon_id, groupnumber, g->identifier); + } else { + g->close[i].type = type; + } + } +} +/* Set the type for all close connections with friendcon_id */ +static void set_conns_status_groups(Group_Chats *g_c, int friendcon_id, uint8_t type) +{ + uint32_t i; + + for (i = 0; i < g_c->num_chats; ++i) { + set_conns_type_close(g_c, i, friendcon_id, type); + } +} + +static int g_handle_status(void *object, int friendcon_id, uint8_t status, void *userdata) +{ + Group_Chats *g_c = (Group_Chats *)object; + + if (status) { /* Went online */ + set_conns_status_groups(g_c, friendcon_id, GROUPCHAT_CLOSE_ONLINE); + } else { /* Went offline */ + set_conns_status_groups(g_c, friendcon_id, GROUPCHAT_CLOSE_CONNECTION); + // TODO(irungentoo): remove timedout connections? + } + + return 0; +} + +static int g_handle_packet(void *object, int friendcon_id, const uint8_t *data, uint16_t length, void *userdata); +static int handle_lossy(void *object, int friendcon_id, const uint8_t *data, uint16_t length, void *userdata); + +/* Add friend to group chat. + * + * return close index on success + * return -1 on failure. + */ +static int add_conn_to_groupchat(Group_Chats *g_c, int friendcon_id, int groupnumber, uint8_t closest, uint8_t lock) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + uint16_t i, ind = MAX_GROUP_CONNECTIONS; + + for (i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { + if (g->close[i].type == GROUPCHAT_CLOSE_NONE) { + ind = i; + continue; + } + + if (g->close[i].number == (uint32_t)friendcon_id) { + g->close[i].closest = closest; + return i; /* Already in list. */ + } + } + + if (ind == MAX_GROUP_CONNECTIONS) { + return -1; + } + + if (lock) { + friend_connection_lock(g_c->fr_c, friendcon_id); + } + + g->close[ind].type = GROUPCHAT_CLOSE_CONNECTION; + g->close[ind].number = friendcon_id; + g->close[ind].closest = closest; + // TODO(irungentoo): + friend_connection_callbacks(g_c->m->fr_c, friendcon_id, GROUPCHAT_CALLBACK_INDEX, &g_handle_status, &g_handle_packet, + &handle_lossy, g_c, friendcon_id); + + return ind; +} + +/* Creates a new groupchat and puts it in the chats array. + * + * type is one of GROUPCHAT_TYPE_* + * + * return group number on success. + * return -1 on failure. + */ +int add_groupchat(Group_Chats *g_c, uint8_t type) +{ + int groupnumber = create_group_chat(g_c); + + if (groupnumber == -1) { + return -1; + } + + Group_c *g = &g_c->chats[groupnumber]; + + g->status = GROUPCHAT_STATUS_CONNECTED; + g->number_joined = -1; + new_symmetric_key(g->identifier + 1); + g->identifier[0] = type; + g->peer_number = 0; /* Founder is peer 0. */ + memcpy(g->real_pk, g_c->m->net_crypto->self_public_key, CRYPTO_PUBLIC_KEY_SIZE); + int peer_index = addpeer(g_c, groupnumber, g->real_pk, g_c->m->dht->self_public_key, 0, NULL, false); + + if (peer_index == -1) { + return -1; + } + + setnick(g_c, groupnumber, peer_index, g_c->m->name, g_c->m->name_length, NULL, false); + + return groupnumber; +} + +static int group_kill_peer_send(const Group_Chats *g_c, int groupnumber, uint16_t peer_num); +/* Delete a groupchat from the chats array. + * + * return 0 on success. + * return -1 if groupnumber is invalid. + */ +int del_groupchat(Group_Chats *g_c, int groupnumber) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + group_kill_peer_send(g_c, groupnumber, g->peer_number); + + unsigned int i; + + for (i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { + if (g->close[i].type == GROUPCHAT_CLOSE_NONE) { + continue; + } + + g->close[i].type = GROUPCHAT_CLOSE_NONE; + kill_friend_connection(g_c->fr_c, g->close[i].number); + } + + for (i = 0; i < g->numpeers; ++i) { + if (g->peer_on_leave) { + g->peer_on_leave(g->object, groupnumber, i, g->group[i].object); + } + } + + free(g->group); + + if (g->group_on_delete) { + g->group_on_delete(g->object, groupnumber); + } + + return wipe_group_chat(g_c, groupnumber); +} + +/* Copy the public key of peernumber who is in groupnumber to pk. + * pk must be CRYPTO_PUBLIC_KEY_SIZE long. + * + * return 0 on success + * return -1 if groupnumber is invalid. + * return -2 if peernumber is invalid. + */ +int group_peer_pubkey(const Group_Chats *g_c, int groupnumber, int peernumber, uint8_t *pk) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + if ((uint32_t)peernumber >= g->numpeers) { + return -2; + } + + memcpy(pk, g->group[peernumber].real_pk, CRYPTO_PUBLIC_KEY_SIZE); + return 0; +} + +/* + * Return the size of peernumber's name. + * + * return -1 if groupnumber is invalid. + * return -2 if peernumber is invalid. + */ +int group_peername_size(const Group_Chats *g_c, int groupnumber, int peernumber) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + if ((uint32_t)peernumber >= g->numpeers) { + return -2; + } + + if (g->group[peernumber].nick_len == 0) { + return 8; + } + + return g->group[peernumber].nick_len; +} + +/* Copy the name of peernumber who is in groupnumber to name. + * name must be at least MAX_NAME_LENGTH long. + * + * return length of name if success + * return -1 if groupnumber is invalid. + * return -2 if peernumber is invalid. + */ +int group_peername(const Group_Chats *g_c, int groupnumber, int peernumber, uint8_t *name) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + if ((uint32_t)peernumber >= g->numpeers) { + return -2; + } + + if (g->group[peernumber].nick_len == 0) { + memcpy(name, "Tox User", 8); + return 8; + } + + memcpy(name, g->group[peernumber].nick, g->group[peernumber].nick_len); + return g->group[peernumber].nick_len; +} + +/* List all the peers in the group chat. + * + * Copies the names of the peers to the name[length][MAX_NAME_LENGTH] array. + * + * Copies the lengths of the names to lengths[length] + * + * returns the number of peers on success. + * + * return -1 on failure. + */ +int group_names(const Group_Chats *g_c, int groupnumber, uint8_t names[][MAX_NAME_LENGTH], uint16_t lengths[], + uint16_t length) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + unsigned int i; + + for (i = 0; i < g->numpeers && i < length; ++i) { + lengths[i] = group_peername(g_c, groupnumber, i, names[i]); + } + + return i; +} + +/* Return the number of peers in the group chat on success. + * return -1 if groupnumber is invalid. + */ +int group_number_peers(const Group_Chats *g_c, int groupnumber) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + return g->numpeers; +} + +/* return 1 if the peernumber corresponds to ours. + * return 0 if the peernumber is not ours. + * return -1 if groupnumber is invalid. + * return -2 if peernumber is invalid. + * return -3 if we are not connected to the group chat. + */ +int group_peernumber_is_ours(const Group_Chats *g_c, int groupnumber, int peernumber) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + if ((uint32_t)peernumber >= g->numpeers) { + return -2; + } + + if (g->status != GROUPCHAT_STATUS_CONNECTED) { + return -3; + } + + return g->peer_number == g->group[peernumber].peer_number; +} + +/* return the type of groupchat (GROUPCHAT_TYPE_) that groupnumber is. + * + * return -1 on failure. + * return type on success. + */ +int group_get_type(const Group_Chats *g_c, int groupnumber) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + return g->identifier[0]; +} + +/* Send a group packet to friendcon_id. + * + * return 1 on success + * return 0 on failure + */ +static unsigned int send_packet_group_peer(Friend_Connections *fr_c, int friendcon_id, uint8_t packet_id, + uint16_t group_num, const uint8_t *data, uint16_t length) +{ + if (1 + sizeof(uint16_t) + length > MAX_CRYPTO_DATA_SIZE) { + return 0; + } + + group_num = net_htons(group_num); + VLA(uint8_t, packet, 1 + sizeof(uint16_t) + length); + packet[0] = packet_id; + memcpy(packet + 1, &group_num, sizeof(uint16_t)); + memcpy(packet + 1 + sizeof(uint16_t), data, length); + return write_cryptpacket(fr_c->net_crypto, friend_connection_crypt_connection_id(fr_c, friendcon_id), packet, + SIZEOF_VLA(packet), 0) != -1; +} + +/* Send a group lossy packet to friendcon_id. + * + * return 1 on success + * return 0 on failure + */ +static unsigned int send_lossy_group_peer(Friend_Connections *fr_c, int friendcon_id, uint8_t packet_id, + uint16_t group_num, const uint8_t *data, uint16_t length) +{ + if (1 + sizeof(uint16_t) + length > MAX_CRYPTO_DATA_SIZE) { + return 0; + } + + group_num = net_htons(group_num); + VLA(uint8_t, packet, 1 + sizeof(uint16_t) + length); + packet[0] = packet_id; + memcpy(packet + 1, &group_num, sizeof(uint16_t)); + memcpy(packet + 1 + sizeof(uint16_t), data, length); + return send_lossy_cryptpacket(fr_c->net_crypto, friend_connection_crypt_connection_id(fr_c, friendcon_id), packet, + SIZEOF_VLA(packet)) != -1; +} + +#define INVITE_PACKET_SIZE (1 + sizeof(uint16_t) + GROUP_IDENTIFIER_LENGTH) +#define INVITE_ID 0 + +#define INVITE_RESPONSE_PACKET_SIZE (1 + sizeof(uint16_t) * 2 + GROUP_IDENTIFIER_LENGTH) +#define INVITE_RESPONSE_ID 1 + +/* invite friendnumber to groupnumber. + * + * return 0 on success. + * return -1 if groupnumber is invalid. + * return -2 if invite packet failed to send. + */ +int invite_friend(Group_Chats *g_c, int32_t friendnumber, int groupnumber) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + uint8_t invite[INVITE_PACKET_SIZE]; + invite[0] = INVITE_ID; + uint16_t groupchat_num = net_htons((uint16_t)groupnumber); + memcpy(invite + 1, &groupchat_num, sizeof(groupchat_num)); + memcpy(invite + 1 + sizeof(groupchat_num), g->identifier, GROUP_IDENTIFIER_LENGTH); + + if (send_conference_invite_packet(g_c->m, friendnumber, invite, sizeof(invite))) { + return 0; + } + + wipe_group_chat(g_c, groupnumber); + return -2; +} + +static unsigned int send_peer_query(Group_Chats *g_c, int friendcon_id, uint16_t group_num); + +/* Join a group (you need to have been invited first.) + * + * expected_type is the groupchat type we expect the chat we are joining is. + * + * return group number on success. + * return -1 if data length is invalid. + * return -2 if group is not the expected type. + * return -3 if friendnumber is invalid. + * return -4 if client is already in this group. + * return -5 if group instance failed to initialize. + * return -6 if join packet fails to send. + */ +int join_groupchat(Group_Chats *g_c, int32_t friendnumber, uint8_t expected_type, const uint8_t *data, uint16_t length) +{ + if (length != sizeof(uint16_t) + GROUP_IDENTIFIER_LENGTH) { + return -1; + } + + if (data[sizeof(uint16_t)] != expected_type) { + return -2; + } + + int friendcon_id = getfriendcon_id(g_c->m, friendnumber); + + if (friendcon_id == -1) { + return -3; + } + + if (get_group_num(g_c, data + sizeof(uint16_t)) != -1) { + return -4; + } + + int groupnumber = create_group_chat(g_c); + + if (groupnumber == -1) { + return -5; + } + + Group_c *g = &g_c->chats[groupnumber]; + + uint16_t group_num = net_htons(groupnumber); + g->status = GROUPCHAT_STATUS_VALID; + g->number_joined = -1; + memcpy(g->real_pk, g_c->m->net_crypto->self_public_key, CRYPTO_PUBLIC_KEY_SIZE); + + uint8_t response[INVITE_RESPONSE_PACKET_SIZE]; + response[0] = INVITE_RESPONSE_ID; + memcpy(response + 1, &group_num, sizeof(uint16_t)); + memcpy(response + 1 + sizeof(uint16_t), data, sizeof(uint16_t) + GROUP_IDENTIFIER_LENGTH); + + if (send_conference_invite_packet(g_c->m, friendnumber, response, sizeof(response))) { + uint16_t other_groupnum; + memcpy(&other_groupnum, data, sizeof(other_groupnum)); + other_groupnum = net_ntohs(other_groupnum); + memcpy(g->identifier, data + sizeof(uint16_t), GROUP_IDENTIFIER_LENGTH); + int close_index = add_conn_to_groupchat(g_c, friendcon_id, groupnumber, 0, 1); + + if (close_index != -1) { + g->close[close_index].group_number = other_groupnum; + g->close[close_index].type = GROUPCHAT_CLOSE_ONLINE; + g->number_joined = friendcon_id; + } + + send_peer_query(g_c, friendcon_id, other_groupnum); + return groupnumber; + } + + g->status = GROUPCHAT_STATUS_NONE; + return -6; +} + +/* Set handlers for custom lossy packets. + * + * NOTE: Handler must return 0 if packet is to be relayed, -1 if the packet should not be relayed. + * + * Function(void *group object (set with group_set_object), int groupnumber, int friendgroupnumber, void *group peer object (set with group_peer_set_object), const uint8_t *packet, uint16_t length) + */ +void group_lossy_packet_registerhandler(Group_Chats *g_c, uint8_t byte, int (*function)(void *, int, int, void *, + const uint8_t *, uint16_t)) +{ + g_c->lossy_packethandlers[byte].function = function; +} + +/* Set the callback for group invites. + * + * Function(Group_Chats *g_c, int32_t friendnumber, uint8_t type, uint8_t *data, uint16_t length, void *userdata) + * + * data of length is what needs to be passed to join_groupchat(). + */ +void g_callback_group_invite(Group_Chats *g_c, void (*function)(Messenger *m, uint32_t, int, const uint8_t *, + size_t, void *)) +{ + g_c->invite_callback = function; +} + +/* Set the callback for group messages. + * + * Function(Group_Chats *g_c, int groupnumber, int friendgroupnumber, uint8_t * message, uint16_t length, void *userdata) + */ +void g_callback_group_message(Group_Chats *g_c, void (*function)(Messenger *m, uint32_t, uint32_t, int, const uint8_t *, + size_t, void *)) +{ + g_c->message_callback = function; +} + +/* Set callback function for peer name list changes. + * + * It gets called every time the name list changes(new peer/name, deleted peer) + * Function(Group_Chats *g_c, int groupnumber, int peernumber, TOX_CHAT_CHANGE change, void *userdata) + */ +void g_callback_group_namelistchange(Group_Chats *g_c, void (*function)(Messenger *m, int, int, uint8_t, void *)) +{ + g_c->group_namelistchange = function; +} + +/* Set callback function for title changes. + * + * Function(Group_Chats *g_c, int groupnumber, int friendgroupnumber, uint8_t * title, uint8_t length, void *userdata) + * if friendgroupnumber == -1, then author is unknown (e.g. initial joining the group) + */ +void g_callback_group_title(Group_Chats *g_c, void (*function)(Messenger *m, uint32_t, uint32_t, const uint8_t *, + size_t, void *)) +{ + g_c->title_callback = function; +} + +/* Set a function to be called when a new peer joins a group chat. + * + * Function(void *group object (set with group_set_object), int groupnumber, int friendgroupnumber) + * + * return 0 on success. + * return -1 on failure. + */ +int callback_groupchat_peer_new(const Group_Chats *g_c, int groupnumber, void (*function)(void *, int, int)) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + g->peer_on_join = function; + return 0; +} + +/* Set a function to be called when a peer leaves a group chat. + * + * Function(void *group object (set with group_set_object), int groupnumber, int friendgroupnumber, void *group peer object (set with group_peer_set_object)) + * + * return 0 on success. + * return -1 on failure. + */ +int callback_groupchat_peer_delete(Group_Chats *g_c, int groupnumber, void (*function)(void *, int, int, void *)) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + g->peer_on_leave = function; + return 0; +} + +/* Set a function to be called when the group chat is deleted. + * + * Function(void *group object (set with group_set_object), int groupnumber) + * + * return 0 on success. + * return -1 on failure. + */ +int callback_groupchat_delete(Group_Chats *g_c, int groupnumber, void (*function)(void *, int)) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + g->group_on_delete = function; + return 0; +} + +static int send_message_group(const Group_Chats *g_c, int groupnumber, uint8_t message_id, const uint8_t *data, + uint16_t len); + +#define GROUP_MESSAGE_PING_ID 0 +static int group_ping_send(const Group_Chats *g_c, int groupnumber) +{ + if (send_message_group(g_c, groupnumber, GROUP_MESSAGE_PING_ID, 0, 0) > 0) { + return 0; + } + + return -1; +} + +#define GROUP_MESSAGE_NEW_PEER_ID 16 +#define GROUP_MESSAGE_NEW_PEER_LENGTH (sizeof(uint16_t) + CRYPTO_PUBLIC_KEY_SIZE * 2) +/* send a new_peer message + * return 0 on success + * return -1 on failure + */ +static int group_new_peer_send(const Group_Chats *g_c, int groupnumber, uint16_t peer_num, const uint8_t *real_pk, + uint8_t *temp_pk) +{ + uint8_t packet[GROUP_MESSAGE_NEW_PEER_LENGTH]; + + peer_num = net_htons(peer_num); + memcpy(packet, &peer_num, sizeof(uint16_t)); + memcpy(packet + sizeof(uint16_t), real_pk, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(packet + sizeof(uint16_t) + CRYPTO_PUBLIC_KEY_SIZE, temp_pk, CRYPTO_PUBLIC_KEY_SIZE); + + if (send_message_group(g_c, groupnumber, GROUP_MESSAGE_NEW_PEER_ID, packet, sizeof(packet)) > 0) { + return 0; + } + + return -1; +} + +#define GROUP_MESSAGE_KILL_PEER_ID 17 +#define GROUP_MESSAGE_KILL_PEER_LENGTH (sizeof(uint16_t)) + +/* send a kill_peer message + * return 0 on success + * return -1 on failure + */ +static int group_kill_peer_send(const Group_Chats *g_c, int groupnumber, uint16_t peer_num) +{ + uint8_t packet[GROUP_MESSAGE_KILL_PEER_LENGTH]; + + peer_num = net_htons(peer_num); + memcpy(packet, &peer_num, sizeof(uint16_t)); + + if (send_message_group(g_c, groupnumber, GROUP_MESSAGE_KILL_PEER_ID, packet, sizeof(packet)) > 0) { + return 0; + } + + return -1; +} + +#define GROUP_MESSAGE_NAME_ID 48 + +/* send a name message + * return 0 on success + * return -1 on failure + */ +static int group_name_send(const Group_Chats *g_c, int groupnumber, const uint8_t *nick, uint16_t nick_len) +{ + if (nick_len > MAX_NAME_LENGTH) { + return -1; + } + + if (send_message_group(g_c, groupnumber, GROUP_MESSAGE_NAME_ID, nick, nick_len) > 0) { + return 0; + } + + return -1; +} + +#define GROUP_MESSAGE_TITLE_ID 49 + +/* set the group's title, limited to MAX_NAME_LENGTH + * return 0 on success + * return -1 if groupnumber is invalid. + * return -2 if title is too long or empty. + * return -3 if packet fails to send. + */ +int group_title_send(const Group_Chats *g_c, int groupnumber, const uint8_t *title, uint8_t title_len) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + if (title_len > MAX_NAME_LENGTH || title_len == 0) { + return -2; + } + + /* same as already set? */ + if (g->title_len == title_len && !memcmp(g->title, title, title_len)) { + return 0; + } + + memcpy(g->title, title, title_len); + g->title_len = title_len; + + if (g->numpeers == 1) { + return 0; + } + + if (send_message_group(g_c, groupnumber, GROUP_MESSAGE_TITLE_ID, title, title_len) > 0) { + return 0; + } + + return -3; +} + +/* return the group's title size. + * return -1 of groupnumber is invalid. + * return -2 if title is too long or empty. + */ +int group_title_get_size(const Group_Chats *g_c, int groupnumber) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + if (g->title_len == 0 || g->title_len > MAX_NAME_LENGTH) { + return -2; + } + + return g->title_len; +} + +/* Get group title from groupnumber and put it in title. + * Title needs to be a valid memory location with a size of at least MAX_NAME_LENGTH (128) bytes. + * + * return length of copied title if success. + * return -1 if groupnumber is invalid. + * return -2 if title is too long or empty. + */ +int group_title_get(const Group_Chats *g_c, int groupnumber, uint8_t *title) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + if (g->title_len == 0 || g->title_len > MAX_NAME_LENGTH) { + return -2; + } + + memcpy(title, g->title, g->title_len); + return g->title_len; +} + +static void handle_friend_invite_packet(Messenger *m, uint32_t friendnumber, const uint8_t *data, uint16_t length, + void *userdata) +{ + Group_Chats *g_c = (Group_Chats *)m->conferences_object; + + if (length <= 1) { + return; + } + + const uint8_t *invite_data = data + 1; + uint16_t invite_length = length - 1; + + switch (data[0]) { + case INVITE_ID: { + if (length != INVITE_PACKET_SIZE) { + return; + } + + int groupnumber = get_group_num(g_c, data + 1 + sizeof(uint16_t)); + + if (groupnumber == -1) { + if (g_c->invite_callback) { + g_c->invite_callback(m, friendnumber, *(invite_data + sizeof(uint16_t)), invite_data, invite_length, userdata); + } + + return; + } + + break; + } + + case INVITE_RESPONSE_ID: { + if (length != INVITE_RESPONSE_PACKET_SIZE) { + return; + } + + uint16_t other_groupnum, groupnum; + memcpy(&groupnum, data + 1 + sizeof(uint16_t), sizeof(uint16_t)); + groupnum = net_ntohs(groupnum); + + Group_c *g = get_group_c(g_c, groupnum); + + if (!g) { + return; + } + + if (crypto_memcmp(data + 1 + sizeof(uint16_t) * 2, g->identifier, GROUP_IDENTIFIER_LENGTH) != 0) { + return; + } + + /* TODO(irungentoo): what if two people enter the group at the same time and + are given the same peer_number by different nodes? */ + uint16_t peer_number = rand(); + + unsigned int tries = 0; + + while (get_peer_index(g, peer_number) != -1) { + peer_number = rand(); + ++tries; + + if (tries > 32) { + return; + } + } + + memcpy(&other_groupnum, data + 1, sizeof(uint16_t)); + other_groupnum = net_ntohs(other_groupnum); + + int friendcon_id = getfriendcon_id(m, friendnumber); + uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE], temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; + get_friendcon_public_keys(real_pk, temp_pk, g_c->fr_c, friendcon_id); + + addpeer(g_c, groupnum, real_pk, temp_pk, peer_number, userdata, true); + int close_index = add_conn_to_groupchat(g_c, friendcon_id, groupnum, 0, 1); + + if (close_index != -1) { + g->close[close_index].group_number = other_groupnum; + g->close[close_index].type = GROUPCHAT_CLOSE_ONLINE; + } + + group_new_peer_send(g_c, groupnum, peer_number, real_pk, temp_pk); + break; + } + + default: + return; + } +} + +/* Find index of friend in the close list; + * + * returns index on success + * returns -1 on failure. + */ +static int friend_in_close(Group_c *g, int friendcon_id) +{ + unsigned int i; + + for (i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { + if (g->close[i].type == GROUPCHAT_CLOSE_NONE) { + continue; + } + + if (g->close[i].number != (uint32_t)friendcon_id) { + continue; + } + + return i; + } + + return -1; +} + +/* return number of connected close connections. + */ +static unsigned int count_close_connected(Group_c *g) +{ + unsigned int i, count = 0; + + for (i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { + if (g->close[i].type == GROUPCHAT_CLOSE_ONLINE) { + ++count; + } + } + + return count; +} + +#define ONLINE_PACKET_DATA_SIZE (sizeof(uint16_t) + GROUP_IDENTIFIER_LENGTH) + +static int send_packet_online(Friend_Connections *fr_c, int friendcon_id, uint16_t group_num, uint8_t *identifier) +{ + uint8_t packet[1 + ONLINE_PACKET_DATA_SIZE]; + group_num = net_htons(group_num); + packet[0] = PACKET_ID_ONLINE_PACKET; + memcpy(packet + 1, &group_num, sizeof(uint16_t)); + memcpy(packet + 1 + sizeof(uint16_t), identifier, GROUP_IDENTIFIER_LENGTH); + return write_cryptpacket(fr_c->net_crypto, friend_connection_crypt_connection_id(fr_c, friendcon_id), packet, + sizeof(packet), 0) != -1; +} + +static unsigned int send_peer_kill(Group_Chats *g_c, int friendcon_id, uint16_t group_num); + +static int handle_packet_online(Group_Chats *g_c, int friendcon_id, const uint8_t *data, uint16_t length) +{ + if (length != ONLINE_PACKET_DATA_SIZE) { + return -1; + } + + int groupnumber = get_group_num(g_c, data + sizeof(uint16_t)); + uint16_t other_groupnum; + memcpy(&other_groupnum, data, sizeof(uint16_t)); + other_groupnum = net_ntohs(other_groupnum); + + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + int index = friend_in_close(g, friendcon_id); + + if (index == -1) { + return -1; + } + + if (g->close[index].type == GROUPCHAT_CLOSE_ONLINE) { + return -1; + } + + if (count_close_connected(g) == 0) { + send_peer_query(g_c, friendcon_id, other_groupnum); + } + + g->close[index].group_number = other_groupnum; + g->close[index].type = GROUPCHAT_CLOSE_ONLINE; + send_packet_online(g_c->fr_c, friendcon_id, groupnumber, g->identifier); + + if (g->number_joined != -1 && count_close_connected(g) >= DESIRED_CLOSE_CONNECTIONS) { + int fr_close_index = friend_in_close(g, g->number_joined); + + if (fr_close_index == -1) { + return -1; + } + + if (!g->close[fr_close_index].closest) { + g->close[fr_close_index].type = GROUPCHAT_CLOSE_NONE; + send_peer_kill(g_c, g->close[fr_close_index].number, g->close[fr_close_index].group_number); + kill_friend_connection(g_c->fr_c, g->close[fr_close_index].number); + g->number_joined = -1; + } + } + + return 0; +} + +#define PEER_KILL_ID 1 +#define PEER_QUERY_ID 8 +#define PEER_RESPONSE_ID 9 +#define PEER_TITLE_ID 10 +// we could send title with invite, but then if it changes between sending and accepting inv, joinee won't see it + +/* return 1 on success. + * return 0 on failure + */ +static unsigned int send_peer_kill(Group_Chats *g_c, int friendcon_id, uint16_t group_num) +{ + uint8_t packet[1]; + packet[0] = PEER_KILL_ID; + return send_packet_group_peer(g_c->fr_c, friendcon_id, PACKET_ID_DIRECT_CONFERENCE, group_num, packet, sizeof(packet)); +} + + +/* return 1 on success. + * return 0 on failure + */ +static unsigned int send_peer_query(Group_Chats *g_c, int friendcon_id, uint16_t group_num) +{ + uint8_t packet[1]; + packet[0] = PEER_QUERY_ID; + return send_packet_group_peer(g_c->fr_c, friendcon_id, PACKET_ID_DIRECT_CONFERENCE, group_num, packet, sizeof(packet)); +} + +/* return number of peers sent on success. + * return 0 on failure. + */ +static unsigned int send_peers(Group_Chats *g_c, int groupnumber, int friendcon_id, uint16_t group_num) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + uint8_t packet[MAX_CRYPTO_DATA_SIZE - (1 + sizeof(uint16_t))]; + packet[0] = PEER_RESPONSE_ID; + uint8_t *p = packet + 1; + + uint16_t sent = 0; + unsigned int i; + + for (i = 0; i < g->numpeers; ++i) { + if ((p - packet) + sizeof(uint16_t) + CRYPTO_PUBLIC_KEY_SIZE * 2 + 1 + g->group[i].nick_len > sizeof(packet)) { + if (send_packet_group_peer(g_c->fr_c, friendcon_id, PACKET_ID_DIRECT_CONFERENCE, group_num, packet, (p - packet))) { + sent = i; + } else { + return sent; + } + + p = packet + 1; + } + + uint16_t peer_num = net_htons(g->group[i].peer_number); + memcpy(p, &peer_num, sizeof(peer_num)); + p += sizeof(peer_num); + memcpy(p, g->group[i].real_pk, CRYPTO_PUBLIC_KEY_SIZE); + p += CRYPTO_PUBLIC_KEY_SIZE; + memcpy(p, g->group[i].temp_pk, CRYPTO_PUBLIC_KEY_SIZE); + p += CRYPTO_PUBLIC_KEY_SIZE; + *p = g->group[i].nick_len; + p += 1; + memcpy(p, g->group[i].nick, g->group[i].nick_len); + p += g->group[i].nick_len; + } + + if (sent != i) { + if (send_packet_group_peer(g_c->fr_c, friendcon_id, PACKET_ID_DIRECT_CONFERENCE, group_num, packet, (p - packet))) { + sent = i; + } + } + + if (g->title_len) { + VLA(uint8_t, Packet, 1 + g->title_len); + Packet[0] = PEER_TITLE_ID; + memcpy(Packet + 1, g->title, g->title_len); + send_packet_group_peer(g_c->fr_c, friendcon_id, PACKET_ID_DIRECT_CONFERENCE, group_num, Packet, SIZEOF_VLA(Packet)); + } + + return sent; +} + +static int handle_send_peers(Group_Chats *g_c, int groupnumber, const uint8_t *data, uint16_t length, void *userdata) +{ + if (length == 0) { + return -1; + } + + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + const uint8_t *d = data; + + while ((unsigned int)(length - (d - data)) >= sizeof(uint16_t) + CRYPTO_PUBLIC_KEY_SIZE * 2 + 1) { + uint16_t peer_num; + memcpy(&peer_num, d, sizeof(peer_num)); + peer_num = net_ntohs(peer_num); + d += sizeof(uint16_t); + int peer_index = addpeer(g_c, groupnumber, d, d + CRYPTO_PUBLIC_KEY_SIZE, peer_num, userdata, true); + + if (peer_index == -1) { + return -1; + } + + if (g->status == GROUPCHAT_STATUS_VALID + && public_key_cmp(d, g_c->m->net_crypto->self_public_key) == 0) { + g->peer_number = peer_num; + g->status = GROUPCHAT_STATUS_CONNECTED; + group_name_send(g_c, groupnumber, g_c->m->name, g_c->m->name_length); + } + + d += CRYPTO_PUBLIC_KEY_SIZE * 2; + uint8_t name_length = *d; + d += 1; + + if (name_length > (length - (d - data)) || name_length > MAX_NAME_LENGTH) { + return -1; + } + + setnick(g_c, groupnumber, peer_index, d, name_length, userdata, true); + d += name_length; + } + + return 0; +} + +static void handle_direct_packet(Group_Chats *g_c, int groupnumber, const uint8_t *data, uint16_t length, + int close_index, void *userdata) +{ + if (length == 0) { + return; + } + + switch (data[0]) { + case PEER_KILL_ID: { + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return; + } + + if (!g->close[close_index].closest) { + g->close[close_index].type = GROUPCHAT_CLOSE_NONE; + kill_friend_connection(g_c->fr_c, g->close[close_index].number); + } + } + + break; + + case PEER_QUERY_ID: { + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return; + } + + send_peers(g_c, groupnumber, g->close[close_index].number, g->close[close_index].group_number); + } + + break; + + case PEER_RESPONSE_ID: { + handle_send_peers(g_c, groupnumber, data + 1, length - 1, userdata); + } + + break; + + case PEER_TITLE_ID: { + settitle(g_c, groupnumber, -1, data + 1, length - 1, userdata); + } + + break; + } +} + +#define MIN_MESSAGE_PACKET_LEN (sizeof(uint16_t) * 2 + sizeof(uint32_t) + 1) + +/* Send message to all close except receiver (if receiver isn't -1) + * NOTE: this function appends the group chat number to the data passed to it. + * + * return number of messages sent. + */ +static unsigned int send_message_all_close(const Group_Chats *g_c, int groupnumber, const uint8_t *data, + uint16_t length, int receiver) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return 0; + } + + uint16_t i, sent = 0; + + for (i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { + if (g->close[i].type != GROUPCHAT_CLOSE_ONLINE) { + continue; + } + + if ((int)i == receiver) { + continue; + } + + if (send_packet_group_peer(g_c->fr_c, g->close[i].number, PACKET_ID_MESSAGE_CONFERENCE, g->close[i].group_number, data, + length)) { + ++sent; + } + } + + return sent; +} + +/* Send lossy message to all close except receiver (if receiver isn't -1) + * NOTE: this function appends the group chat number to the data passed to it. + * + * return number of messages sent. + */ +static unsigned int send_lossy_all_close(const Group_Chats *g_c, int groupnumber, const uint8_t *data, uint16_t length, + int receiver) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return 0; + } + + unsigned int i, sent = 0, num_connected_closest = 0, connected_closest[DESIRED_CLOSE_CONNECTIONS]; + + for (i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { + if (g->close[i].type != GROUPCHAT_CLOSE_ONLINE) { + continue; + } + + if ((int)i == receiver) { + continue; + } + + if (g->close[i].closest) { + connected_closest[num_connected_closest] = i; + ++num_connected_closest; + continue; + } + + if (send_lossy_group_peer(g_c->fr_c, g->close[i].number, PACKET_ID_LOSSY_CONFERENCE, g->close[i].group_number, data, + length)) { + ++sent; + } + } + + if (!num_connected_closest) { + return sent; + } + + unsigned int to_send = 0; + uint64_t comp_val_old = ~0; + + for (i = 0; i < num_connected_closest; ++i) { + uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t dht_temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; + get_friendcon_public_keys(real_pk, dht_temp_pk, g_c->fr_c, g->close[connected_closest[i]].number); + uint64_t comp_val = calculate_comp_value(g->real_pk, real_pk); + + if (comp_val < comp_val_old) { + to_send = connected_closest[i]; + comp_val_old = comp_val; + } + } + + if (send_lossy_group_peer(g_c->fr_c, g->close[to_send].number, PACKET_ID_LOSSY_CONFERENCE, + g->close[to_send].group_number, data, length)) { + ++sent; + } + + unsigned int to_send_other = 0; + comp_val_old = ~0; + + for (i = 0; i < num_connected_closest; ++i) { + uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t dht_temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; + get_friendcon_public_keys(real_pk, dht_temp_pk, g_c->fr_c, g->close[connected_closest[i]].number); + uint64_t comp_val = calculate_comp_value(real_pk, g->real_pk); + + if (comp_val < comp_val_old) { + to_send_other = connected_closest[i]; + comp_val_old = comp_val; + } + } + + if (to_send_other == to_send) { + return sent; + } + + if (send_lossy_group_peer(g_c->fr_c, g->close[to_send_other].number, PACKET_ID_LOSSY_CONFERENCE, + g->close[to_send_other].group_number, data, length)) { + ++sent; + } + + return sent; +} + +#define MAX_GROUP_MESSAGE_DATA_LEN (MAX_CRYPTO_DATA_SIZE - (1 + MIN_MESSAGE_PACKET_LEN)) + +/* Send data of len with message_id to groupnumber. + * + * return number of peers it was sent to on success. + * return -1 if groupnumber is invalid. + * return -2 if message is too long. + * return -3 if we are not connected to the group. + * reutrn -4 if message failed to send. + */ +static int send_message_group(const Group_Chats *g_c, int groupnumber, uint8_t message_id, const uint8_t *data, + uint16_t len) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + if (len > MAX_GROUP_MESSAGE_DATA_LEN) { + return -2; + } + + if (g->status != GROUPCHAT_STATUS_CONNECTED) { + return -3; + } + + VLA(uint8_t, packet, sizeof(uint16_t) + sizeof(uint32_t) + 1 + len); + uint16_t peer_num = net_htons(g->peer_number); + memcpy(packet, &peer_num, sizeof(peer_num)); + + ++g->message_number; + + if (!g->message_number) { + ++g->message_number; + } + + uint32_t message_num = net_htonl(g->message_number); + memcpy(packet + sizeof(uint16_t), &message_num, sizeof(message_num)); + + packet[sizeof(uint16_t) + sizeof(uint32_t)] = message_id; + + if (len) { + memcpy(packet + sizeof(uint16_t) + sizeof(uint32_t) + 1, data, len); + } + + unsigned int ret = send_message_all_close(g_c, groupnumber, packet, SIZEOF_VLA(packet), -1); + + return (ret == 0) ? -4 : ret; +} + +/* send a group message + * return 0 on success + * see: send_message_group() for error codes. + */ +int group_message_send(const Group_Chats *g_c, int groupnumber, const uint8_t *message, uint16_t length) +{ + int ret = send_message_group(g_c, groupnumber, PACKET_ID_MESSAGE, message, length); + + if (ret > 0) { + return 0; + } + + return ret; +} + +/* send a group action + * return 0 on success + * see: send_message_group() for error codes. + */ +int group_action_send(const Group_Chats *g_c, int groupnumber, const uint8_t *action, uint16_t length) +{ + int ret = send_message_group(g_c, groupnumber, PACKET_ID_ACTION, action, length); + + if (ret > 0) { + return 0; + } + + return ret; +} + +/* High level function to send custom lossy packets. + * + * return -1 on failure. + * return 0 on success. + */ +int send_group_lossy_packet(const Group_Chats *g_c, int groupnumber, const uint8_t *data, uint16_t length) +{ + // TODO(irungentoo): length check here? + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + VLA(uint8_t, packet, sizeof(uint16_t) * 2 + length); + uint16_t peer_number = net_htons(g->peer_number); + memcpy(packet, &peer_number, sizeof(uint16_t)); + uint16_t message_num = net_htons(g->lossy_message_number); + memcpy(packet + sizeof(uint16_t), &message_num, sizeof(uint16_t)); + memcpy(packet + sizeof(uint16_t) * 2, data, length); + + if (send_lossy_all_close(g_c, groupnumber, packet, SIZEOF_VLA(packet), -1) == 0) { + return -1; + } + + ++g->lossy_message_number; + return 0; +} + +static void handle_message_packet_group(Group_Chats *g_c, int groupnumber, const uint8_t *data, uint16_t length, + int close_index, void *userdata) +{ + if (length < sizeof(uint16_t) + sizeof(uint32_t) + 1) { + return; + } + + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return; + } + + uint16_t peer_number; + memcpy(&peer_number, data, sizeof(uint16_t)); + peer_number = net_ntohs(peer_number); + + int index = get_peer_index(g, peer_number); + + if (index == -1) { + /* We don't know the peer this packet came from so we query the list of peers from that peer. + (They would not have relayed it if they didn't know the peer.) */ + send_peer_query(g_c, g->close[close_index].number, g->close[close_index].group_number); + return; + } + + uint32_t message_number; + memcpy(&message_number, data + sizeof(uint16_t), sizeof(message_number)); + message_number = net_ntohl(message_number); + + if (g->group[index].last_message_number == 0) { + g->group[index].last_message_number = message_number; + } else if (message_number - g->group[index].last_message_number > 64 || + message_number == g->group[index].last_message_number) { + return; + } + + g->group[index].last_message_number = message_number; + + uint8_t message_id = data[sizeof(uint16_t) + sizeof(message_number)]; + const uint8_t *msg_data = data + sizeof(uint16_t) + sizeof(message_number) + 1; + uint16_t msg_data_len = length - (sizeof(uint16_t) + sizeof(message_number) + 1); + + switch (message_id) { + case GROUP_MESSAGE_PING_ID: { + if (msg_data_len != 0) { + return; + } + + g->group[index].last_recv = unix_time(); + } + break; + + case GROUP_MESSAGE_NEW_PEER_ID: { + if (msg_data_len != GROUP_MESSAGE_NEW_PEER_LENGTH) { + return; + } + + uint16_t new_peer_number; + memcpy(&new_peer_number, msg_data, sizeof(uint16_t)); + new_peer_number = net_ntohs(new_peer_number); + addpeer(g_c, groupnumber, msg_data + sizeof(uint16_t), msg_data + sizeof(uint16_t) + CRYPTO_PUBLIC_KEY_SIZE, + new_peer_number, userdata, true); + } + break; + + case GROUP_MESSAGE_KILL_PEER_ID: { + if (msg_data_len != GROUP_MESSAGE_KILL_PEER_LENGTH) { + return; + } + + uint16_t kill_peer_number; + memcpy(&kill_peer_number, msg_data, sizeof(uint16_t)); + kill_peer_number = net_ntohs(kill_peer_number); + + if (peer_number == kill_peer_number) { + delpeer(g_c, groupnumber, index, userdata); + } else { + return; + // TODO(irungentoo): + } + } + break; + + case GROUP_MESSAGE_NAME_ID: { + if (setnick(g_c, groupnumber, index, msg_data, msg_data_len, userdata, true) == -1) { + return; + } + } + break; + + case GROUP_MESSAGE_TITLE_ID: { + if (settitle(g_c, groupnumber, index, msg_data, msg_data_len, userdata) == -1) { + return; + } + } + break; + + case PACKET_ID_MESSAGE: { + if (msg_data_len == 0) { + return; + } + + VLA(uint8_t, newmsg, msg_data_len + 1); + memcpy(newmsg, msg_data, msg_data_len); + newmsg[msg_data_len] = 0; + + // TODO(irungentoo): + if (g_c->message_callback) { + g_c->message_callback(g_c->m, groupnumber, index, 0, newmsg, msg_data_len, userdata); + } + + break; + } + + case PACKET_ID_ACTION: { + if (msg_data_len == 0) { + return; + } + + VLA(uint8_t, newmsg, msg_data_len + 1); + memcpy(newmsg, msg_data, msg_data_len); + newmsg[msg_data_len] = 0; + + // TODO(irungentoo): + if (g_c->message_callback) { + g_c->message_callback(g_c->m, groupnumber, index, 1, newmsg, msg_data_len, userdata); + } + + break; + } + + default: + return; + } + + send_message_all_close(g_c, groupnumber, data, length, -1/* TODO(irungentoo) close_index */); +} + +static int g_handle_packet(void *object, int friendcon_id, const uint8_t *data, uint16_t length, void *userdata) +{ + Group_Chats *g_c = (Group_Chats *)object; + + if (length < 1 + sizeof(uint16_t) + 1) { + return -1; + } + + if (data[0] == PACKET_ID_ONLINE_PACKET) { + return handle_packet_online(g_c, friendcon_id, data + 1, length - 1); + } + + if (data[0] != PACKET_ID_DIRECT_CONFERENCE && data[0] != PACKET_ID_MESSAGE_CONFERENCE) { + return -1; + } + + uint16_t groupnumber; + memcpy(&groupnumber, data + 1, sizeof(uint16_t)); + groupnumber = net_ntohs(groupnumber); + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + int index = friend_in_close(g, friendcon_id); + + if (index == -1) { + return -1; + } + + switch (data[0]) { + case PACKET_ID_DIRECT_CONFERENCE: { + handle_direct_packet(g_c, groupnumber, data + 1 + sizeof(uint16_t), length - (1 + sizeof(uint16_t)), index, userdata); + break; + } + + case PACKET_ID_MESSAGE_CONFERENCE: { + handle_message_packet_group(g_c, groupnumber, data + 1 + sizeof(uint16_t), length - (1 + sizeof(uint16_t)), index, + userdata); + break; + } + + default: { + return 0; + } + } + + return 0; +} + +/* Did we already receive the lossy packet or not. + * + * return -1 on failure. + * return 0 if packet was not received. + * return 1 if packet was received. + * + * TODO(irungentoo): test this + */ +static unsigned int lossy_packet_not_received(Group_c *g, int peer_index, uint16_t message_number) +{ + if (peer_index == -1) { + return -1; + } + + if (g->group[peer_index].bottom_lossy_number == g->group[peer_index].top_lossy_number) { + g->group[peer_index].top_lossy_number = message_number; + g->group[peer_index].bottom_lossy_number = (message_number - MAX_LOSSY_COUNT) + 1; + g->group[peer_index].recv_lossy[message_number % MAX_LOSSY_COUNT] = 1; + return 0; + } + + if ((uint16_t)(message_number - g->group[peer_index].bottom_lossy_number) < MAX_LOSSY_COUNT) { + if (g->group[peer_index].recv_lossy[message_number % MAX_LOSSY_COUNT]) { + return 1; + } + + g->group[peer_index].recv_lossy[message_number % MAX_LOSSY_COUNT] = 1; + return 0; + } + + if ((uint16_t)(message_number - g->group[peer_index].bottom_lossy_number) > (1 << 15)) { + return -1; + } + + uint16_t top_distance = message_number - g->group[peer_index].top_lossy_number; + + if (top_distance >= MAX_LOSSY_COUNT) { + crypto_memzero(g->group[peer_index].recv_lossy, sizeof(g->group[peer_index].recv_lossy)); + g->group[peer_index].top_lossy_number = message_number; + g->group[peer_index].bottom_lossy_number = (message_number - MAX_LOSSY_COUNT) + 1; + g->group[peer_index].recv_lossy[message_number % MAX_LOSSY_COUNT] = 1; + return 0; + } + + if (top_distance < MAX_LOSSY_COUNT) { + unsigned int i; + + for (i = g->group[peer_index].bottom_lossy_number; i != (g->group[peer_index].bottom_lossy_number + top_distance); + ++i) { + g->group[peer_index].recv_lossy[i % MAX_LOSSY_COUNT] = 0; + } + + g->group[peer_index].top_lossy_number = message_number; + g->group[peer_index].bottom_lossy_number = (message_number - MAX_LOSSY_COUNT) + 1; + g->group[peer_index].recv_lossy[message_number % MAX_LOSSY_COUNT] = 1; + return 0; + } + + return -1; +} + +static int handle_lossy(void *object, int friendcon_id, const uint8_t *data, uint16_t length, void *userdata) +{ + Group_Chats *g_c = (Group_Chats *)object; + + if (length < 1 + sizeof(uint16_t) * 3 + 1) { + return -1; + } + + if (data[0] != PACKET_ID_LOSSY_CONFERENCE) { + return -1; + } + + uint16_t groupnumber, peer_number, message_number; + memcpy(&groupnumber, data + 1, sizeof(uint16_t)); + memcpy(&peer_number, data + 1 + sizeof(uint16_t), sizeof(uint16_t)); + memcpy(&message_number, data + 1 + sizeof(uint16_t) * 2, sizeof(uint16_t)); + groupnumber = net_ntohs(groupnumber); + peer_number = net_ntohs(peer_number); + message_number = net_ntohs(message_number); + + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + int index = friend_in_close(g, friendcon_id); + + if (index == -1) { + return -1; + } + + if (peer_number == g->peer_number) { + return -1; + } + + int peer_index = get_peer_index(g, peer_number); + + if (peer_index == -1) { + return -1; + } + + if (lossy_packet_not_received(g, peer_index, message_number)) { + return -1; + } + + const uint8_t *lossy_data = data + 1 + sizeof(uint16_t) * 3; + uint16_t lossy_length = length - (1 + sizeof(uint16_t) * 3); + uint8_t message_id = lossy_data[0]; + ++lossy_data; + --lossy_length; + + if (g_c->lossy_packethandlers[message_id].function) { + if (g_c->lossy_packethandlers[message_id].function(g->object, groupnumber, peer_index, g->group[peer_index].object, + lossy_data, lossy_length) == -1) { + return -1; + } + } else { + return -1; + } + + send_lossy_all_close(g_c, groupnumber, data + 1 + sizeof(uint16_t), length - (1 + sizeof(uint16_t)), index); + return 0; +} + +/* Set the object that is tied to the group chat. + * + * return 0 on success. + * return -1 on failure + */ +int group_set_object(const Group_Chats *g_c, int groupnumber, void *object) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + g->object = object; + return 0; +} + +/* Set the object that is tied to the group peer. + * + * return 0 on success. + * return -1 on failure + */ +int group_peer_set_object(const Group_Chats *g_c, int groupnumber, int peernumber, void *object) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + if ((uint32_t)peernumber >= g->numpeers) { + return -1; + } + + g->group[peernumber].object = object; + return 0; +} + +/* Return the object tide to the group chat previously set by group_set_object. + * + * return NULL on failure. + * return object on success. + */ +void *group_get_object(const Group_Chats *g_c, int groupnumber) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return NULL; + } + + return g->object; +} + +/* Return the object tide to the group chat peer previously set by group_peer_set_object. + * + * return NULL on failure. + * return object on success. + */ +void *group_peer_get_object(const Group_Chats *g_c, int groupnumber, int peernumber) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return NULL; + } + + if ((uint32_t)peernumber >= g->numpeers) { + return NULL; + } + + return g->group[peernumber].object; +} + +/* Interval in seconds to send ping messages */ +#define GROUP_PING_INTERVAL 20 + +static int ping_groupchat(Group_Chats *g_c, int groupnumber) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + if (is_timeout(g->last_sent_ping, GROUP_PING_INTERVAL)) { + if (group_ping_send(g_c, groupnumber) != -1) { /* Ping */ + g->last_sent_ping = unix_time(); + } + } + + return 0; +} + +static int groupchat_clear_timedout(Group_Chats *g_c, int groupnumber, void *userdata) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + uint32_t i; + + for (i = 0; i < g->numpeers; ++i) { + if (g->peer_number != g->group[i].peer_number && is_timeout(g->group[i].last_recv, GROUP_PING_INTERVAL * 3)) { + delpeer(g_c, groupnumber, i, userdata); + } + + if (g->group == NULL || i >= g->numpeers) { + break; + } + } + + return 0; +} + +/* Send current name (set in messenger) to all online groups. + */ +void send_name_all_groups(Group_Chats *g_c) +{ + unsigned int i; + + for (i = 0; i < g_c->num_chats; ++i) { + Group_c *g = get_group_c(g_c, i); + + if (!g) { + continue; + } + + if (g->status == GROUPCHAT_STATUS_CONNECTED) { + group_name_send(g_c, i, g_c->m->name, g_c->m->name_length); + } + } +} + +/* Create new groupchat instance. */ +Group_Chats *new_groupchats(Messenger *m) +{ + if (!m) { + return NULL; + } + + Group_Chats *temp = (Group_Chats *)calloc(1, sizeof(Group_Chats)); + + if (temp == NULL) { + return NULL; + } + + temp->m = m; + temp->fr_c = m->fr_c; + m->conferences_object = temp; + m_callback_conference_invite(m, &handle_friend_invite_packet); + + return temp; +} + +/* main groupchats loop. */ +void do_groupchats(Group_Chats *g_c, void *userdata) +{ + unsigned int i; + + for (i = 0; i < g_c->num_chats; ++i) { + Group_c *g = get_group_c(g_c, i); + + if (!g) { + continue; + } + + if (g->status == GROUPCHAT_STATUS_CONNECTED) { + connect_to_closest(g_c, i, userdata); + ping_groupchat(g_c, i); + groupchat_clear_timedout(g_c, i, userdata); + } + } + + // TODO(irungentoo): +} + +/* Free everything related with group chats. */ +void kill_groupchats(Group_Chats *g_c) +{ + unsigned int i; + + for (i = 0; i < g_c->num_chats; ++i) { + del_groupchat(g_c, i); + } + + m_callback_conference_invite(g_c->m, NULL); + g_c->m->conferences_object = NULL; + free(g_c); +} + +/* 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(Group_Chats *g_c) +{ + uint32_t ret = 0; + uint32_t i; + + for (i = 0; i < g_c->num_chats; i++) { + if (g_c->chats[i].status != GROUPCHAT_STATUS_NONE) { + 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(Group_Chats *g_c, uint32_t *out_list, uint32_t list_size) +{ + if (!out_list) { + return 0; + } + + if (g_c->num_chats == 0) { + return 0; + } + + uint32_t i, ret = 0; + + for (i = 0; i < g_c->num_chats; ++i) { + if (ret >= list_size) { + break; /* Abandon ship */ + } + + if (g_c->chats[i].status > GROUPCHAT_STATUS_NONE) { + out_list[ret] = i; + ret++; + } + } + + return ret; +} diff --git a/libs/libtox/src/toxcore/group.h b/libs/libtox/src/toxcore/group.h new file mode 100644 index 0000000000..2e014da36d --- /dev/null +++ b/libs/libtox/src/toxcore/group.h @@ -0,0 +1,395 @@ +/* + * Slightly better groupchats implementation. + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2014 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifndef GROUP_H +#define GROUP_H + +#include "Messenger.h" + +enum { + GROUPCHAT_STATUS_NONE, + GROUPCHAT_STATUS_VALID, + GROUPCHAT_STATUS_CONNECTED +}; + +enum { + GROUPCHAT_TYPE_TEXT, + GROUPCHAT_TYPE_AV +}; + +#define MAX_LOSSY_COUNT 256 + +typedef struct { + uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; + + uint64_t last_recv; + uint32_t last_message_number; + + uint8_t nick[MAX_NAME_LENGTH]; + uint8_t nick_len; + + uint16_t peer_number; + + uint8_t recv_lossy[MAX_LOSSY_COUNT]; + uint16_t bottom_lossy_number, top_lossy_number; + + void *object; +} Group_Peer; + +#define DESIRED_CLOSE_CONNECTIONS 4 +#define MAX_GROUP_CONNECTIONS 16 +#define GROUP_IDENTIFIER_LENGTH (1 + CRYPTO_SYMMETRIC_KEY_SIZE) /* type + CRYPTO_SYMMETRIC_KEY_SIZE so we can use new_symmetric_key(...) to fill it */ + +enum { + GROUPCHAT_CLOSE_NONE, + GROUPCHAT_CLOSE_CONNECTION, + GROUPCHAT_CLOSE_ONLINE +}; + +typedef struct { + uint8_t status; + + Group_Peer *group; + uint32_t numpeers; + + struct { + uint8_t type; /* GROUPCHAT_CLOSE_* */ + uint8_t closest; + uint32_t number; + uint16_t group_number; + } close[MAX_GROUP_CONNECTIONS]; + + uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; + struct { + uint8_t entry; + uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; + } closest_peers[DESIRED_CLOSE_CONNECTIONS]; + uint8_t changed; + + uint8_t identifier[GROUP_IDENTIFIER_LENGTH]; + + uint8_t title[MAX_NAME_LENGTH]; + uint8_t title_len; + + uint32_t message_number; + uint16_t lossy_message_number; + uint16_t peer_number; + + uint64_t last_sent_ping; + + int number_joined; /* friendcon_id of person that invited us to the chat. (-1 means none) */ + + void *object; + + void (*peer_on_join)(void *, int, int); + void (*peer_on_leave)(void *, int, int, void *); + void (*group_on_delete)(void *, int); +} Group_c; + +typedef struct { + Messenger *m; + Friend_Connections *fr_c; + + Group_c *chats; + uint32_t num_chats; + + void (*invite_callback)(Messenger *m, uint32_t, int, const uint8_t *, size_t, void *); + void (*message_callback)(Messenger *m, uint32_t, uint32_t, int, const uint8_t *, size_t, void *); + void (*group_namelistchange)(Messenger *m, int, int, uint8_t, void *); + void (*title_callback)(Messenger *m, uint32_t, uint32_t, const uint8_t *, size_t, void *); + + struct { + int (*function)(void *, int, int, void *, const uint8_t *, uint16_t); + } lossy_packethandlers[256]; +} Group_Chats; + +/* Set the callback for group invites. + * + * Function(Group_Chats *g_c, int32_t friendnumber, uint8_t type, uint8_t *data, uint16_t length, void *userdata) + * + * data of length is what needs to be passed to join_groupchat(). + */ +void g_callback_group_invite(Group_Chats *g_c, void (*function)(Messenger *m, uint32_t, int, const uint8_t *, + size_t, void *)); + +/* Set the callback for group messages. + * + * Function(Group_Chats *g_c, int groupnumber, int friendgroupnumber, uint8_t * message, uint16_t length, void *userdata) + */ +void g_callback_group_message(Group_Chats *g_c, void (*function)(Messenger *m, uint32_t, uint32_t, int, const uint8_t *, + size_t, void *)); + + +/* Set callback function for title changes. + * + * Function(Group_Chats *g_c, int groupnumber, int friendgroupnumber, uint8_t * title, uint8_t length, void *userdata) + * if friendgroupnumber == -1, then author is unknown (e.g. initial joining the group) + */ +void g_callback_group_title(Group_Chats *g_c, void (*function)(Messenger *m, uint32_t, uint32_t, const uint8_t *, + size_t, void *)); + +/* Set callback function for peer name list changes. + * + * It gets called every time the name list changes(new peer/name, deleted peer) + * Function(Group_Chats *g_c, int groupnumber, int peernumber, TOX_CHAT_CHANGE change, void *userdata) + */ +enum { + CHAT_CHANGE_PEER_ADD, + CHAT_CHANGE_PEER_DEL, + CHAT_CHANGE_PEER_NAME, +}; +void g_callback_group_namelistchange(Group_Chats *g_c, void (*function)(Messenger *m, int, int, uint8_t, void *)); + +/* Creates a new groupchat and puts it in the chats array. + * + * type is one of GROUPCHAT_TYPE_* + * + * return group number on success. + * return -1 on failure. + */ +int add_groupchat(Group_Chats *g_c, uint8_t type); + +/* Delete a groupchat from the chats array. + * + * return 0 on success. + * return -1 if groupnumber is invalid. + */ +int del_groupchat(Group_Chats *g_c, int groupnumber); + +/* Copy the public key of peernumber who is in groupnumber to pk. + * pk must be CRYPTO_PUBLIC_KEY_SIZE long. + * + * return 0 on success + * return -1 if groupnumber is invalid. + * return -2 if peernumber is invalid. + */ +int group_peer_pubkey(const Group_Chats *g_c, int groupnumber, int peernumber, uint8_t *pk); + +/* + * Return the size of peernumber's name. + * + * return -1 if groupnumber is invalid. + * return -2 if peernumber is invalid. + */ +int group_peername_size(const Group_Chats *g_c, int groupnumber, int peernumber); + +/* Copy the name of peernumber who is in groupnumber to name. + * name must be at least MAX_NAME_LENGTH long. + * + * return length of name if success + * return -1 if groupnumber is invalid. + * return -2 if peernumber is invalid. + */ +int group_peername(const Group_Chats *g_c, int groupnumber, int peernumber, uint8_t *name); + +/* invite friendnumber to groupnumber + * + * return 0 on success. + * return -1 if groupnumber is invalid. + * return -2 if invite packet failed to send. + */ +int invite_friend(Group_Chats *g_c, int32_t friendnumber, int groupnumber); + +/* Join a group (you need to have been invited first.) + * + * expected_type is the groupchat type we expect the chat we are joining is. + * + * return group number on success. + * return -1 if data length is invalid. + * return -2 if group is not the expected type. + * return -3 if friendnumber is invalid. + * return -4 if client is already in this group. + * return -5 if group instance failed to initialize. + * return -6 if join packet fails to send. + */ +int join_groupchat(Group_Chats *g_c, int32_t friendnumber, uint8_t expected_type, const uint8_t *data, uint16_t length); + +/* send a group message + * return 0 on success + * see: send_message_group() for error codes. + */ +int group_message_send(const Group_Chats *g_c, int groupnumber, const uint8_t *message, uint16_t length); + +/* send a group action + * return 0 on success + * see: send_message_group() for error codes. + */ +int group_action_send(const Group_Chats *g_c, int groupnumber, const uint8_t *action, uint16_t length); + +/* set the group's title, limited to MAX_NAME_LENGTH + * return 0 on success + * return -1 if groupnumber is invalid. + * return -2 if title is too long or empty. + * return -3 if packet fails to send. + */ +int group_title_send(const Group_Chats *g_c, int groupnumber, const uint8_t *title, uint8_t title_len); + + +/* return the group's title size. + * return -1 of groupnumber is invalid. + * return -2 if title is too long or empty. + */ +int group_title_get_size(const Group_Chats *g_c, int groupnumber); + +/* Get group title from groupnumber and put it in title. + * Title needs to be a valid memory location with a size of at least MAX_NAME_LENGTH (128) bytes. + * + * return length of copied title if success. + * return -1 if groupnumber is invalid. + * return -2 if title is too long or empty. + */ +int group_title_get(const Group_Chats *g_c, int groupnumber, uint8_t *title); + +/* Return the number of peers in the group chat on success. + * return -1 if groupnumber is invalid. + */ +int group_number_peers(const Group_Chats *g_c, int groupnumber); + +/* return 1 if the peernumber corresponds to ours. + * return 0 if the peernumber is not ours. + * return -1 if groupnumber is invalid. + * return -2 if peernumber is invalid. + * return -3 if we are not connected to the group chat. + */ +int group_peernumber_is_ours(const Group_Chats *g_c, int groupnumber, int peernumber); + +/* List all the peers in the group chat. + * + * Copies the names of the peers to the name[length][MAX_NAME_LENGTH] array. + * + * Copies the lengths of the names to lengths[length] + * + * returns the number of peers on success. + * + * return -1 on failure. + */ +int group_names(const Group_Chats *g_c, int groupnumber, uint8_t names[][MAX_NAME_LENGTH], uint16_t lengths[], + uint16_t length); + +/* Set handlers for custom lossy packets. + * + * NOTE: Handler must return 0 if packet is to be relayed, -1 if the packet should not be relayed. + * + * Function(void *group object (set with group_set_object), int groupnumber, int friendgroupnumber, void *group peer object (set with group_peer_set_object), const uint8_t *packet, uint16_t length) + */ +void group_lossy_packet_registerhandler(Group_Chats *g_c, uint8_t byte, int (*function)(void *, int, int, void *, + const uint8_t *, uint16_t)); + +/* High level function to send custom lossy packets. + * + * return -1 on failure. + * return 0 on success. + */ +int send_group_lossy_packet(const Group_Chats *g_c, int groupnumber, const uint8_t *data, 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 count_chatlist(Group_Chats *g_c); + +/* 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(Group_Chats *g_c, uint32_t *out_list, uint32_t list_size); + +/* return the type of groupchat (GROUPCHAT_TYPE_) that groupnumber is. + * + * return -1 on failure. + * return type on success. + */ +int group_get_type(const Group_Chats *g_c, int groupnumber); + +/* Send current name (set in messenger) to all online groups. + */ +void send_name_all_groups(Group_Chats *g_c); + +/* Set the object that is tied to the group chat. + * + * return 0 on success. + * return -1 on failure + */ +int group_set_object(const Group_Chats *g_c, int groupnumber, void *object); + +/* Set the object that is tied to the group peer. + * + * return 0 on success. + * return -1 on failure + */ +int group_peer_set_object(const Group_Chats *g_c, int groupnumber, int peernumber, void *object); + +/* Return the object tide to the group chat previously set by group_set_object. + * + * return NULL on failure. + * return object on success. + */ +void *group_get_object(const Group_Chats *g_c, int groupnumber); + +/* Return the object tide to the group chat peer previously set by group_peer_set_object. + * + * return NULL on failure. + * return object on success. + */ +void *group_peer_get_object(const Group_Chats *g_c, int groupnumber, int peernumber); + +/* Set a function to be called when a new peer joins a group chat. + * + * Function(void *group object (set with group_set_object), int groupnumber, int friendgroupnumber) + * + * return 0 on success. + * return -1 on failure. + */ +int callback_groupchat_peer_new(const Group_Chats *g_c, int groupnumber, void (*function)(void *, int, int)); + +/* Set a function to be called when a peer leaves a group chat. + * + * Function(void *group object (set with group_set_object), int groupnumber, int friendgroupnumber, void *group peer object (set with group_peer_set_object)) + * + * return 0 on success. + * return -1 on failure. + */ +int callback_groupchat_peer_delete(Group_Chats *g_c, int groupnumber, void (*function)(void *, int, int, void *)); + +/* Set a function to be called when the group chat is deleted. + * + * Function(void *group object (set with group_set_object), int groupnumber) + * + * return 0 on success. + * return -1 on failure. + */ +int callback_groupchat_delete(Group_Chats *g_c, int groupnumber, void (*function)(void *, int)); + +/* Create new groupchat instance. */ +Group_Chats *new_groupchats(Messenger *m); + +/* main groupchats loop. */ +void do_groupchats(Group_Chats *g_c, void *userdata); + +/* Free everything related with group chats. */ +void kill_groupchats(Group_Chats *g_c); + +#endif diff --git a/libs/libtox/src/toxcore/list.c b/libs/libtox/src/toxcore/list.c new file mode 100644 index 0000000000..36d609fbd1 --- /dev/null +++ b/libs/libtox/src/toxcore/list.c @@ -0,0 +1,266 @@ +/* + * Simple struct with functions to create a list which associates ids with data + * -Allows for finding ids associated with data such as IPs or public keys in a short time + * -Should only be used if there are relatively few add/remove calls to the list + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2014 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "list.h" + +/* Basically, the elements in the list are placed in order so that they can be searched for easily + * -each element is seen as a big-endian integer when ordering them + * -the ids array is maintained so that each id always matches + * -the search algorithm cuts down the time to find the id associated with a piece of data + * at the cost of slow add/remove functions for large lists + * -Starts at 1/2 of the array, compares the element in the array with the data, + * then moves +/- 1/4 of the array depending on whether the value is greater or lower, + * then +- 1/8, etc, until the value is matched or its position where it should be in the array is found + * -some considerations since the array size is never perfect + */ + +#define INDEX(i) (~i) + +/* Find data in list + * + * return value: + * >= 0 : index of data in array + * < 0 : no match, returns index (return value is INDEX(index)) where + * the data should be inserted + */ +static int find(const BS_LIST *list, const uint8_t *data) +{ + //should work well, but could be improved + if (list->n == 0) { + return INDEX(0); + } + + uint32_t i = list->n / 2; //current position in the array + uint32_t delta = i / 2; //how much we move in the array + + if (!delta) { + delta = 1; + } + + int d = -1; //used to determine if closest match is found + //closest match is found if we move back to where we have already been + + while (1) { + int r = memcmp(data, list->data + list->element_size * i, list->element_size); + + if (r == 0) { + return i; + } + + if (r > 0) { + //data is greater + //move down + i += delta; + + if (d == 0 || i == list->n) { + //reached bottom of list, or closest match + return INDEX(i); + } + + delta = (delta) / 2; + + if (delta == 0) { + delta = 1; + d = 1; + } + } else { + //data is smaller + if (d == 1 || i == 0) { + //reached top or list or closest match + return INDEX(i); + } + + //move up + i -= delta; + + delta = (delta) / 2; + + if (delta == 0) { + delta = 1; + d = 0; + } + } + } +} + +/* Resized the list list + * + * return value: + * 1 : success + * 0 : failure + */ +static int resize(BS_LIST *list, uint32_t new_size) +{ + if (new_size == 0) { + bs_list_free(list); + return 1; + } + + uint8_t *data = (uint8_t *)realloc(list->data, list->element_size * new_size); + + if (!data) { + return 0; + } + + list->data = data; + + int *ids = (int *)realloc(list->ids, sizeof(int) * new_size); + + if (!ids) { + return 0; + } + + list->ids = ids; + + return 1; +} + + +int bs_list_init(BS_LIST *list, uint32_t element_size, uint32_t initial_capacity) +{ + //set initial values + list->n = 0; + list->element_size = element_size; + list->capacity = 0; + list->data = NULL; + list->ids = NULL; + + if (initial_capacity != 0) { + if (!resize(list, initial_capacity)) { + return 0; + } + } + + list->capacity = initial_capacity; + + return 1; +} + +void bs_list_free(BS_LIST *list) +{ + //free both arrays + free(list->data); + list->data = NULL; + + free(list->ids); + list->ids = NULL; +} + +int bs_list_find(const BS_LIST *list, const uint8_t *data) +{ + int r = find(list, data); + + //return only -1 and positive values + if (r < 0) { + return -1; + } + + return list->ids[r]; +} + +int bs_list_add(BS_LIST *list, const uint8_t *data, int id) +{ + //find where the new element should be inserted + //see: return value of find() + int i = find(list, data); + + if (i >= 0) { + //already in list + return 0; + } + + i = ~i; + + //increase the size of the arrays if needed + if (list->n == list->capacity) { + // 1.5 * n + 1 + const uint32_t new_capacity = list->n + list->n / 2 + 1; + + if (!resize(list, new_capacity)) { + return 0; + } + + list->capacity = new_capacity; + } + + //insert data to element array + memmove(list->data + (i + 1) * list->element_size, list->data + i * list->element_size, + (list->n - i) * list->element_size); + memcpy(list->data + i * list->element_size, data, list->element_size); + + //insert id to id array + memmove(&list->ids[i + 1], &list->ids[i], (list->n - i) * sizeof(int)); + list->ids[i] = id; + + //increase n + list->n++; + + return 1; +} + +int bs_list_remove(BS_LIST *list, const uint8_t *data, int id) +{ + int i = find(list, data); + + if (i < 0) { + return 0; + } + + if (list->ids[i] != id) { + //this should never happen + return 0; + } + + //decrease the size of the arrays if needed + if (list->n < list->capacity / 2) { + const uint32_t new_capacity = list->capacity / 2; + + if (resize(list, new_capacity)) { + list->capacity = new_capacity; + } + } + + list->n--; + + memmove(list->data + i * list->element_size, list->data + (i + 1) * list->element_size, + (list->n - i) * list->element_size); + memmove(&list->ids[i], &list->ids[i + 1], (list->n - i) * sizeof(int)); + + return 1; +} + +int bs_list_trim(BS_LIST *list) +{ + if (!resize(list, list->n)) { + return 0; + } + + list->capacity = list->n; + return 1; +} diff --git a/libs/libtox/src/toxcore/list.h b/libs/libtox/src/toxcore/list.h new file mode 100644 index 0000000000..cb3b328c5a --- /dev/null +++ b/libs/libtox/src/toxcore/list.h @@ -0,0 +1,85 @@ +/* + * Simple struct with functions to create a list which associates ids with data + * -Allows for finding ids associated with data such as IPs or public keys in a short time + * -Should only be used if there are relatively few add/remove calls to the list + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2014 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifndef LIST_H +#define LIST_H + +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +typedef struct { + uint32_t n; //number of elements + uint32_t capacity; //number of elements memory is allocated for + uint32_t element_size; //size of the elements + uint8_t *data; //array of elements + int *ids; //array of element ids +} BS_LIST; + +/* Initialize a list, element_size is the size of the elements in the list and + * initial_capacity is the number of elements the memory will be initially allocated for + * + * return value: + * 1 : success + * 0 : failure + */ +int bs_list_init(BS_LIST *list, uint32_t element_size, uint32_t initial_capacity); + +/* Free a list initiated with list_init */ +void bs_list_free(BS_LIST *list); + +/* Retrieve the id of an element in the list + * + * return value: + * >= 0 : id associated with data + * -1 : failure + */ +int bs_list_find(const BS_LIST *list, const uint8_t *data); + +/* Add an element with associated id to the list + * + * return value: + * 1 : success + * 0 : failure (data already in list) + */ +int bs_list_add(BS_LIST *list, const uint8_t *data, int id); + +/* Remove element from the list + * + * return value: + * 1 : success + * 0 : failure (element not found or id does not match) + */ +int bs_list_remove(BS_LIST *list, const uint8_t *data, int id); + +/* Removes the memory overhead + * + * return value: + * 1 : success + * 0 : failure + */ +int bs_list_trim(BS_LIST *list); + +#endif diff --git a/libs/libtox/src/toxcore/logger.c b/libs/libtox/src/toxcore/logger.c new file mode 100644 index 0000000000..18b765a385 --- /dev/null +++ b/libs/libtox/src/toxcore/logger.c @@ -0,0 +1,77 @@ +/* + * Text logging abstraction. + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2013,2015 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "logger.h" + +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> + + +struct Logger { + logger_cb *callback; + void *context; + void *userdata; +}; + + +/** + * Public Functions + */ +Logger *logger_new(void) +{ + return (Logger *)calloc(1, sizeof(Logger)); +} + +void logger_kill(Logger *log) +{ + free(log); +} + +void logger_callback_log(Logger *log, logger_cb *function, void *context, void *userdata) +{ + log->callback = function; + log->context = context; + log->userdata = userdata; +} + +void logger_write(Logger *log, LOGGER_LEVEL level, const char *file, int line, const char *func, const char *format, + ...) +{ + if (!log || !log->callback) { + return; + } + + /* Format message */ + char msg[1024]; + va_list args; + va_start(args, format); + vsnprintf(msg, sizeof msg, format, args); + va_end(args); + + log->callback(log->context, level, file, line, func, msg, log->userdata); +} diff --git a/libs/libtox/src/toxcore/logger.h b/libs/libtox/src/toxcore/logger.h new file mode 100644 index 0000000000..8c8a639bec --- /dev/null +++ b/libs/libtox/src/toxcore/logger.h @@ -0,0 +1,80 @@ +/* + * Logger abstraction backed by callbacks for writing. + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2013 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifndef TOXLOGGER_H +#define TOXLOGGER_H + +#include <stdint.h> + +#ifndef MIN_LOGGER_LEVEL +#define MIN_LOGGER_LEVEL LOG_INFO +#endif + +typedef enum { + LOG_TRACE, + LOG_DEBUG, + LOG_INFO, + LOG_WARNING, + LOG_ERROR +} LOGGER_LEVEL; + +typedef struct Logger Logger; + +typedef void logger_cb(void *context, LOGGER_LEVEL level, const char *file, int line, + const char *func, const char *message, void *userdata); + +/** + * Creates a new logger with logging disabled (callback is NULL) by default. + */ +Logger *logger_new(void); + +void logger_kill(Logger *log); + +/** + * Sets the logger callback. Disables logging if set to NULL. + * The context parameter is passed to the callback as first argument. + */ +void logger_callback_log(Logger *log, logger_cb *function, void *context, void *userdata); + +/** + * Main write function. If logging disabled does nothing. + */ +void logger_write(Logger *log, LOGGER_LEVEL level, const char *file, int line, const char *func, const char *format, + ...); + + +#define LOGGER_WRITE(log, level, ...) \ + do { \ + if (level >= MIN_LOGGER_LEVEL) { \ + logger_write(log, level, __FILE__, __LINE__, __func__, __VA_ARGS__); \ + } \ + } while (0) + +/* To log with an logger */ +#define LOGGER_TRACE(log, ...) LOGGER_WRITE(log, LOG_TRACE , __VA_ARGS__) +#define LOGGER_DEBUG(log, ...) LOGGER_WRITE(log, LOG_DEBUG , __VA_ARGS__) +#define LOGGER_INFO(log, ...) LOGGER_WRITE(log, LOG_INFO , __VA_ARGS__) +#define LOGGER_WARNING(log, ...) LOGGER_WRITE(log, LOG_WARNING, __VA_ARGS__) +#define LOGGER_ERROR(log, ...) LOGGER_WRITE(log, LOG_ERROR , __VA_ARGS__) + +#endif /* TOXLOGGER_H */ diff --git a/libs/libtox/src/toxcore/net_crypto.c b/libs/libtox/src/toxcore/net_crypto.c new file mode 100644 index 0000000000..37cbab188f --- /dev/null +++ b/libs/libtox/src/toxcore/net_crypto.c @@ -0,0 +1,2908 @@ +/* + * Functions for the core network crypto. + * + * NOTE: This code has to be perfect. We don't mess around with encryption. + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2013 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "net_crypto.h" + +#include "util.h" + +#include <math.h> + + +static uint8_t crypt_connection_id_not_valid(const Net_Crypto *c, int crypt_connection_id) +{ + if ((uint32_t)crypt_connection_id >= c->crypto_connections_length) { + return 1; + } + + if (c->crypto_connections == NULL) { + return 1; + } + + if (c->crypto_connections[crypt_connection_id].status == CRYPTO_CONN_NO_CONNECTION) { + return 1; + } + + return 0; +} + +/* cookie timeout in seconds */ +#define COOKIE_TIMEOUT 15 +#define COOKIE_DATA_LENGTH (CRYPTO_PUBLIC_KEY_SIZE * 2) +#define COOKIE_CONTENTS_LENGTH (sizeof(uint64_t) + COOKIE_DATA_LENGTH) +#define COOKIE_LENGTH (CRYPTO_NONCE_SIZE + COOKIE_CONTENTS_LENGTH + CRYPTO_MAC_SIZE) + +#define COOKIE_REQUEST_PLAIN_LENGTH (COOKIE_DATA_LENGTH + sizeof(uint64_t)) +#define COOKIE_REQUEST_LENGTH (1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + COOKIE_REQUEST_PLAIN_LENGTH + CRYPTO_MAC_SIZE) +#define COOKIE_RESPONSE_LENGTH (1 + CRYPTO_NONCE_SIZE + COOKIE_LENGTH + sizeof(uint64_t) + CRYPTO_MAC_SIZE) + +/* Create a cookie request packet and put it in packet. + * dht_public_key is the dht public key of the other + * + * packet must be of size COOKIE_REQUEST_LENGTH or bigger. + * + * return -1 on failure. + * return COOKIE_REQUEST_LENGTH on success. + */ +static int create_cookie_request(const Net_Crypto *c, uint8_t *packet, uint8_t *dht_public_key, uint64_t number, + uint8_t *shared_key) +{ + uint8_t plain[COOKIE_REQUEST_PLAIN_LENGTH]; + uint8_t padding[CRYPTO_PUBLIC_KEY_SIZE] = {0}; + + memcpy(plain, c->self_public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(plain + CRYPTO_PUBLIC_KEY_SIZE, padding, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(plain + (CRYPTO_PUBLIC_KEY_SIZE * 2), &number, sizeof(uint64_t)); + + DHT_get_shared_key_sent(c->dht, shared_key, dht_public_key); + uint8_t nonce[CRYPTO_NONCE_SIZE]; + random_nonce(nonce); + packet[0] = NET_PACKET_COOKIE_REQUEST; + memcpy(packet + 1, c->dht->self_public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, nonce, CRYPTO_NONCE_SIZE); + int len = encrypt_data_symmetric(shared_key, nonce, plain, sizeof(plain), + packet + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE); + + if (len != COOKIE_REQUEST_PLAIN_LENGTH + CRYPTO_MAC_SIZE) { + return -1; + } + + return (1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + len); +} + +/* Create cookie of length COOKIE_LENGTH from bytes of length COOKIE_DATA_LENGTH using encryption_key + * + * return -1 on failure. + * return 0 on success. + */ +static int create_cookie(uint8_t *cookie, const uint8_t *bytes, const uint8_t *encryption_key) +{ + uint8_t contents[COOKIE_CONTENTS_LENGTH]; + uint64_t temp_time = unix_time(); + memcpy(contents, &temp_time, sizeof(temp_time)); + memcpy(contents + sizeof(temp_time), bytes, COOKIE_DATA_LENGTH); + random_nonce(cookie); + int len = encrypt_data_symmetric(encryption_key, cookie, contents, sizeof(contents), cookie + CRYPTO_NONCE_SIZE); + + if (len != COOKIE_LENGTH - CRYPTO_NONCE_SIZE) { + return -1; + } + + return 0; +} + +/* Open cookie of length COOKIE_LENGTH to bytes of length COOKIE_DATA_LENGTH using encryption_key + * + * return -1 on failure. + * return 0 on success. + */ +static int open_cookie(uint8_t *bytes, const uint8_t *cookie, const uint8_t *encryption_key) +{ + uint8_t contents[COOKIE_CONTENTS_LENGTH]; + int len = decrypt_data_symmetric(encryption_key, cookie, cookie + CRYPTO_NONCE_SIZE, + COOKIE_LENGTH - CRYPTO_NONCE_SIZE, contents); + + if (len != sizeof(contents)) { + return -1; + } + + uint64_t cookie_time; + memcpy(&cookie_time, contents, sizeof(cookie_time)); + uint64_t temp_time = unix_time(); + + if (cookie_time + COOKIE_TIMEOUT < temp_time || temp_time < cookie_time) { + return -1; + } + + memcpy(bytes, contents + sizeof(cookie_time), COOKIE_DATA_LENGTH); + return 0; +} + + +/* Create a cookie response packet and put it in packet. + * request_plain must be COOKIE_REQUEST_PLAIN_LENGTH bytes. + * packet must be of size COOKIE_RESPONSE_LENGTH or bigger. + * + * return -1 on failure. + * return COOKIE_RESPONSE_LENGTH on success. + */ +static int create_cookie_response(const Net_Crypto *c, uint8_t *packet, const uint8_t *request_plain, + const uint8_t *shared_key, const uint8_t *dht_public_key) +{ + uint8_t cookie_plain[COOKIE_DATA_LENGTH]; + memcpy(cookie_plain, request_plain, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(cookie_plain + CRYPTO_PUBLIC_KEY_SIZE, dht_public_key, CRYPTO_PUBLIC_KEY_SIZE); + uint8_t plain[COOKIE_LENGTH + sizeof(uint64_t)]; + + if (create_cookie(plain, cookie_plain, c->secret_symmetric_key) != 0) { + return -1; + } + + memcpy(plain + COOKIE_LENGTH, request_plain + COOKIE_DATA_LENGTH, sizeof(uint64_t)); + packet[0] = NET_PACKET_COOKIE_RESPONSE; + random_nonce(packet + 1); + int len = encrypt_data_symmetric(shared_key, packet + 1, plain, sizeof(plain), packet + 1 + CRYPTO_NONCE_SIZE); + + if (len != COOKIE_RESPONSE_LENGTH - (1 + CRYPTO_NONCE_SIZE)) { + return -1; + } + + return COOKIE_RESPONSE_LENGTH; +} + +/* Handle the cookie request packet of length length. + * Put what was in the request in request_plain (must be of size COOKIE_REQUEST_PLAIN_LENGTH) + * Put the key used to decrypt the request into shared_key (of size CRYPTO_SHARED_KEY_SIZE) for use in the response. + * + * return -1 on failure. + * return 0 on success. + */ +static int handle_cookie_request(const Net_Crypto *c, uint8_t *request_plain, uint8_t *shared_key, + uint8_t *dht_public_key, const uint8_t *packet, uint16_t length) +{ + if (length != COOKIE_REQUEST_LENGTH) { + return -1; + } + + memcpy(dht_public_key, packet + 1, CRYPTO_PUBLIC_KEY_SIZE); + DHT_get_shared_key_sent(c->dht, shared_key, dht_public_key); + int len = decrypt_data_symmetric(shared_key, packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, + packet + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE, COOKIE_REQUEST_PLAIN_LENGTH + CRYPTO_MAC_SIZE, + request_plain); + + if (len != COOKIE_REQUEST_PLAIN_LENGTH) { + return -1; + } + + return 0; +} + +/* Handle the cookie request packet (for raw UDP) + */ +static int udp_handle_cookie_request(void *object, IP_Port source, const uint8_t *packet, uint16_t length, + void *userdata) +{ + Net_Crypto *c = (Net_Crypto *)object; + uint8_t request_plain[COOKIE_REQUEST_PLAIN_LENGTH]; + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + uint8_t dht_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + + if (handle_cookie_request(c, request_plain, shared_key, dht_public_key, packet, length) != 0) { + return 1; + } + + uint8_t data[COOKIE_RESPONSE_LENGTH]; + + if (create_cookie_response(c, data, request_plain, shared_key, dht_public_key) != sizeof(data)) { + return 1; + } + + if ((uint32_t)sendpacket(c->dht->net, source, data, sizeof(data)) != sizeof(data)) { + return 1; + } + + return 0; +} + +/* Handle the cookie request packet (for TCP) + */ +static int tcp_handle_cookie_request(Net_Crypto *c, int connections_number, const uint8_t *packet, uint16_t length) +{ + uint8_t request_plain[COOKIE_REQUEST_PLAIN_LENGTH]; + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + uint8_t dht_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + + if (handle_cookie_request(c, request_plain, shared_key, dht_public_key, packet, length) != 0) { + return -1; + } + + uint8_t data[COOKIE_RESPONSE_LENGTH]; + + if (create_cookie_response(c, data, request_plain, shared_key, dht_public_key) != sizeof(data)) { + return -1; + } + + int ret = send_packet_tcp_connection(c->tcp_c, connections_number, data, sizeof(data)); + return ret; +} + +/* Handle the cookie request packet (for TCP oob packets) + */ +static int tcp_oob_handle_cookie_request(const Net_Crypto *c, unsigned int tcp_connections_number, + const uint8_t *dht_public_key, const uint8_t *packet, uint16_t length) +{ + uint8_t request_plain[COOKIE_REQUEST_PLAIN_LENGTH]; + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + uint8_t dht_public_key_temp[CRYPTO_PUBLIC_KEY_SIZE]; + + if (handle_cookie_request(c, request_plain, shared_key, dht_public_key_temp, packet, length) != 0) { + return -1; + } + + if (public_key_cmp(dht_public_key, dht_public_key_temp) != 0) { + return -1; + } + + uint8_t data[COOKIE_RESPONSE_LENGTH]; + + if (create_cookie_response(c, data, request_plain, shared_key, dht_public_key) != sizeof(data)) { + return -1; + } + + int ret = tcp_send_oob_packet(c->tcp_c, tcp_connections_number, dht_public_key, data, sizeof(data)); + return ret; +} + +/* Handle a cookie response packet of length encrypted with shared_key. + * put the cookie in the response in cookie + * + * cookie must be of length COOKIE_LENGTH. + * + * return -1 on failure. + * return COOKIE_LENGTH on success. + */ +static int handle_cookie_response(uint8_t *cookie, uint64_t *number, const uint8_t *packet, uint16_t length, + const uint8_t *shared_key) +{ + if (length != COOKIE_RESPONSE_LENGTH) { + return -1; + } + + uint8_t plain[COOKIE_LENGTH + sizeof(uint64_t)]; + int len = decrypt_data_symmetric(shared_key, packet + 1, packet + 1 + CRYPTO_NONCE_SIZE, + length - (1 + CRYPTO_NONCE_SIZE), plain); + + if (len != sizeof(plain)) { + return -1; + } + + memcpy(cookie, plain, COOKIE_LENGTH); + memcpy(number, plain + COOKIE_LENGTH, sizeof(uint64_t)); + return COOKIE_LENGTH; +} + +#define HANDSHAKE_PACKET_LENGTH (1 + COOKIE_LENGTH + CRYPTO_NONCE_SIZE + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SHA512_SIZE + COOKIE_LENGTH + CRYPTO_MAC_SIZE) + +/* Create a handshake packet and put it in packet. + * cookie must be COOKIE_LENGTH bytes. + * packet must be of size HANDSHAKE_PACKET_LENGTH or bigger. + * + * return -1 on failure. + * return HANDSHAKE_PACKET_LENGTH on success. + */ +static int create_crypto_handshake(const Net_Crypto *c, uint8_t *packet, const uint8_t *cookie, const uint8_t *nonce, + const uint8_t *session_pk, const uint8_t *peer_real_pk, const uint8_t *peer_dht_pubkey) +{ + uint8_t plain[CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SHA512_SIZE + COOKIE_LENGTH]; + memcpy(plain, nonce, CRYPTO_NONCE_SIZE); + memcpy(plain + CRYPTO_NONCE_SIZE, session_pk, CRYPTO_PUBLIC_KEY_SIZE); + crypto_sha512(plain + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE, cookie, COOKIE_LENGTH); + uint8_t cookie_plain[COOKIE_DATA_LENGTH]; + memcpy(cookie_plain, peer_real_pk, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(cookie_plain + CRYPTO_PUBLIC_KEY_SIZE, peer_dht_pubkey, CRYPTO_PUBLIC_KEY_SIZE); + + if (create_cookie(plain + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SHA512_SIZE, cookie_plain, + c->secret_symmetric_key) != 0) { + return -1; + } + + random_nonce(packet + 1 + COOKIE_LENGTH); + int len = encrypt_data(peer_real_pk, c->self_secret_key, packet + 1 + COOKIE_LENGTH, plain, sizeof(plain), + packet + 1 + COOKIE_LENGTH + CRYPTO_NONCE_SIZE); + + if (len != HANDSHAKE_PACKET_LENGTH - (1 + COOKIE_LENGTH + CRYPTO_NONCE_SIZE)) { + return -1; + } + + packet[0] = NET_PACKET_CRYPTO_HS; + memcpy(packet + 1, cookie, COOKIE_LENGTH); + + return HANDSHAKE_PACKET_LENGTH; +} + +/* Handle a crypto handshake packet of length. + * put the nonce contained in the packet in nonce, + * the session public key in session_pk + * the real public key of the peer in peer_real_pk + * the dht public key of the peer in dht_public_key and + * the cookie inside the encrypted part of the packet in cookie. + * + * if expected_real_pk isn't NULL it denotes the real public key + * the packet should be from. + * + * nonce must be at least CRYPTO_NONCE_SIZE + * session_pk must be at least CRYPTO_PUBLIC_KEY_SIZE + * peer_real_pk must be at least CRYPTO_PUBLIC_KEY_SIZE + * cookie must be at least COOKIE_LENGTH + * + * return -1 on failure. + * return 0 on success. + */ +static int handle_crypto_handshake(const Net_Crypto *c, uint8_t *nonce, uint8_t *session_pk, uint8_t *peer_real_pk, + uint8_t *dht_public_key, uint8_t *cookie, const uint8_t *packet, uint16_t length, const uint8_t *expected_real_pk) +{ + if (length != HANDSHAKE_PACKET_LENGTH) { + return -1; + } + + uint8_t cookie_plain[COOKIE_DATA_LENGTH]; + + if (open_cookie(cookie_plain, packet + 1, c->secret_symmetric_key) != 0) { + return -1; + } + + if (expected_real_pk) { + if (public_key_cmp(cookie_plain, expected_real_pk) != 0) { + return -1; + } + } + + uint8_t cookie_hash[CRYPTO_SHA512_SIZE]; + crypto_sha512(cookie_hash, packet + 1, COOKIE_LENGTH); + + uint8_t plain[CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SHA512_SIZE + COOKIE_LENGTH]; + int len = decrypt_data(cookie_plain, c->self_secret_key, packet + 1 + COOKIE_LENGTH, + packet + 1 + COOKIE_LENGTH + CRYPTO_NONCE_SIZE, + HANDSHAKE_PACKET_LENGTH - (1 + COOKIE_LENGTH + CRYPTO_NONCE_SIZE), plain); + + if (len != sizeof(plain)) { + return -1; + } + + if (crypto_memcmp(cookie_hash, plain + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE, + CRYPTO_SHA512_SIZE) != 0) { + return -1; + } + + memcpy(nonce, plain, CRYPTO_NONCE_SIZE); + memcpy(session_pk, plain + CRYPTO_NONCE_SIZE, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(cookie, plain + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SHA512_SIZE, COOKIE_LENGTH); + memcpy(peer_real_pk, cookie_plain, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(dht_public_key, cookie_plain + CRYPTO_PUBLIC_KEY_SIZE, CRYPTO_PUBLIC_KEY_SIZE); + return 0; +} + + +static Crypto_Connection *get_crypto_connection(const Net_Crypto *c, int crypt_connection_id) +{ + if (crypt_connection_id_not_valid(c, crypt_connection_id)) { + return 0; + } + + return &c->crypto_connections[crypt_connection_id]; +} + + +/* Associate an ip_port to a connection. + * + * return -1 on failure. + * return 0 on success. + */ +static int add_ip_port_connection(Net_Crypto *c, int crypt_connection_id, IP_Port ip_port) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) { + return -1; + } + + if (ip_port.ip.family == TOX_AF_INET) { + if (!ipport_equal(&ip_port, &conn->ip_portv4) && LAN_ip(conn->ip_portv4.ip) != 0) { + if (!bs_list_add(&c->ip_port_list, (uint8_t *)&ip_port, crypt_connection_id)) { + return -1; + } + + bs_list_remove(&c->ip_port_list, (uint8_t *)&conn->ip_portv4, crypt_connection_id); + conn->ip_portv4 = ip_port; + return 0; + } + } else if (ip_port.ip.family == TOX_AF_INET6) { + if (!ipport_equal(&ip_port, &conn->ip_portv6)) { + if (!bs_list_add(&c->ip_port_list, (uint8_t *)&ip_port, crypt_connection_id)) { + return -1; + } + + bs_list_remove(&c->ip_port_list, (uint8_t *)&conn->ip_portv6, crypt_connection_id); + conn->ip_portv6 = ip_port; + return 0; + } + } + + return -1; +} + +/* Return the IP_Port that should be used to send packets to the other peer. + * + * return IP_Port with family 0 on failure. + * return IP_Port on success. + */ +static IP_Port return_ip_port_connection(Net_Crypto *c, int crypt_connection_id) +{ + const IP_Port empty = {{0}}; + + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) { + return empty; + } + + uint64_t current_time = unix_time(); + bool v6 = 0, v4 = 0; + + if ((UDP_DIRECT_TIMEOUT + conn->direct_lastrecv_timev4) > current_time) { + v4 = 1; + } + + if ((UDP_DIRECT_TIMEOUT + conn->direct_lastrecv_timev6) > current_time) { + v6 = 1; + } + + if (v4 && LAN_ip(conn->ip_portv4.ip) == 0) { + return conn->ip_portv4; + } + + if (v6 && conn->ip_portv6.ip.family == TOX_AF_INET6) { + return conn->ip_portv6; + } + + if (conn->ip_portv4.ip.family == TOX_AF_INET) { + return conn->ip_portv4; + } + + return empty; +} + +/* Sends a packet to the peer using the fastest route. + * + * return -1 on failure. + * return 0 on success. + */ +static int send_packet_to(Net_Crypto *c, int crypt_connection_id, const uint8_t *data, uint16_t length) +{ +// TODO(irungentoo): TCP, etc... + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) { + return -1; + } + + int direct_send_attempt = 0; + + pthread_mutex_lock(&conn->mutex); + IP_Port ip_port = return_ip_port_connection(c, crypt_connection_id); + + // TODO(irungentoo): on bad networks, direct connections might not last indefinitely. + if (ip_port.ip.family != 0) { + bool direct_connected = 0; + crypto_connection_status(c, crypt_connection_id, &direct_connected, NULL); + + if (direct_connected) { + if ((uint32_t)sendpacket(c->dht->net, ip_port, data, length) == length) { + pthread_mutex_unlock(&conn->mutex); + return 0; + } + + pthread_mutex_unlock(&conn->mutex); + return -1; + } + + // TODO(irungentoo): a better way of sending packets directly to confirm the others ip. + uint64_t current_time = unix_time(); + + if ((((UDP_DIRECT_TIMEOUT / 2) + conn->direct_send_attempt_time) > current_time && length < 96) + || data[0] == NET_PACKET_COOKIE_REQUEST || data[0] == NET_PACKET_CRYPTO_HS) { + if ((uint32_t)sendpacket(c->dht->net, ip_port, data, length) == length) { + direct_send_attempt = 1; + conn->direct_send_attempt_time = unix_time(); + } + } + } + + pthread_mutex_unlock(&conn->mutex); + pthread_mutex_lock(&c->tcp_mutex); + int ret = send_packet_tcp_connection(c->tcp_c, conn->connection_number_tcp, data, length); + pthread_mutex_unlock(&c->tcp_mutex); + + pthread_mutex_lock(&conn->mutex); + + if (ret == 0) { + conn->last_tcp_sent = current_time_monotonic(); + } + + pthread_mutex_unlock(&conn->mutex); + + if (ret == 0 || direct_send_attempt) { + return 0; + } + + return -1; +} + +/** START: Array Related functions **/ + + +/* Return number of packets in array + * Note that holes are counted too. + */ +static uint32_t num_packets_array(const Packets_Array *array) +{ + return array->buffer_end - array->buffer_start; +} + +/* Add data with packet number to array. + * + * return -1 on failure. + * return 0 on success. + */ +static int add_data_to_buffer(Packets_Array *array, uint32_t number, const Packet_Data *data) +{ + if (number - array->buffer_start > CRYPTO_PACKET_BUFFER_SIZE) { + return -1; + } + + uint32_t num = number % CRYPTO_PACKET_BUFFER_SIZE; + + if (array->buffer[num]) { + return -1; + } + + Packet_Data *new_d = (Packet_Data *)malloc(sizeof(Packet_Data)); + + if (new_d == NULL) { + return -1; + } + + memcpy(new_d, data, sizeof(Packet_Data)); + array->buffer[num] = new_d; + + if ((number - array->buffer_start) >= (array->buffer_end - array->buffer_start)) { + array->buffer_end = number + 1; + } + + return 0; +} + +/* Get pointer of data with packet number. + * + * return -1 on failure. + * return 0 if data at number is empty. + * return 1 if data pointer was put in data. + */ +static int get_data_pointer(const Packets_Array *array, Packet_Data **data, uint32_t number) +{ + uint32_t num_spots = array->buffer_end - array->buffer_start; + + if (array->buffer_end - number > num_spots || number - array->buffer_start >= num_spots) { + return -1; + } + + uint32_t num = number % CRYPTO_PACKET_BUFFER_SIZE; + + if (!array->buffer[num]) { + return 0; + } + + *data = array->buffer[num]; + return 1; +} + +/* Add data to end of array. + * + * return -1 on failure. + * return packet number on success. + */ +static int64_t add_data_end_of_buffer(Packets_Array *array, const Packet_Data *data) +{ + if (num_packets_array(array) >= CRYPTO_PACKET_BUFFER_SIZE) { + return -1; + } + + Packet_Data *new_d = (Packet_Data *)malloc(sizeof(Packet_Data)); + + if (new_d == NULL) { + return -1; + } + + memcpy(new_d, data, sizeof(Packet_Data)); + uint32_t id = array->buffer_end; + array->buffer[id % CRYPTO_PACKET_BUFFER_SIZE] = new_d; + ++array->buffer_end; + return id; +} + +/* Read data from begginning of array. + * + * return -1 on failure. + * return packet number on success. + */ +static int64_t read_data_beg_buffer(Packets_Array *array, Packet_Data *data) +{ + if (array->buffer_end == array->buffer_start) { + return -1; + } + + uint32_t num = array->buffer_start % CRYPTO_PACKET_BUFFER_SIZE; + + if (!array->buffer[num]) { + return -1; + } + + memcpy(data, array->buffer[num], sizeof(Packet_Data)); + uint32_t id = array->buffer_start; + ++array->buffer_start; + free(array->buffer[num]); + array->buffer[num] = NULL; + return id; +} + +/* Delete all packets in array before number (but not number) + * + * return -1 on failure. + * return 0 on success + */ +static int clear_buffer_until(Packets_Array *array, uint32_t number) +{ + uint32_t num_spots = array->buffer_end - array->buffer_start; + + if (array->buffer_end - number >= num_spots || number - array->buffer_start > num_spots) { + return -1; + } + + uint32_t i; + + for (i = array->buffer_start; i != number; ++i) { + uint32_t num = i % CRYPTO_PACKET_BUFFER_SIZE; + + if (array->buffer[num]) { + free(array->buffer[num]); + array->buffer[num] = NULL; + } + } + + array->buffer_start = i; + return 0; +} + +static int clear_buffer(Packets_Array *array) +{ + uint32_t i; + + for (i = array->buffer_start; i != array->buffer_end; ++i) { + uint32_t num = i % CRYPTO_PACKET_BUFFER_SIZE; + + if (array->buffer[num]) { + free(array->buffer[num]); + array->buffer[num] = NULL; + } + } + + array->buffer_start = i; + return 0; +} + +/* Set array buffer end to number. + * + * return -1 on failure. + * return 0 on success. + */ +static int set_buffer_end(Packets_Array *array, uint32_t number) +{ + if ((number - array->buffer_start) > CRYPTO_PACKET_BUFFER_SIZE) { + return -1; + } + + if ((number - array->buffer_end) > CRYPTO_PACKET_BUFFER_SIZE) { + return -1; + } + + array->buffer_end = number; + return 0; +} + +/* Create a packet request packet from recv_array and send_buffer_end into + * data of length. + * + * return -1 on failure. + * return length of packet on success. + */ +static int generate_request_packet(uint8_t *data, uint16_t length, const Packets_Array *recv_array) +{ + if (length == 0) { + return -1; + } + + data[0] = PACKET_ID_REQUEST; + + uint16_t cur_len = 1; + + if (recv_array->buffer_start == recv_array->buffer_end) { + return cur_len; + } + + if (length <= cur_len) { + return cur_len; + } + + uint32_t i, n = 1; + + for (i = recv_array->buffer_start; i != recv_array->buffer_end; ++i) { + uint32_t num = i % CRYPTO_PACKET_BUFFER_SIZE; + + if (!recv_array->buffer[num]) { + data[cur_len] = n; + n = 0; + ++cur_len; + + if (length <= cur_len) { + return cur_len; + } + } else if (n == 255) { + data[cur_len] = 0; + n = 0; + ++cur_len; + + if (length <= cur_len) { + return cur_len; + } + } + + ++n; + } + + return cur_len; +} + +/* Handle a request data packet. + * Remove all the packets the other received from the array. + * + * return -1 on failure. + * return number of requested packets on success. + */ +static int handle_request_packet(Packets_Array *send_array, const uint8_t *data, uint16_t length, + uint64_t *latest_send_time, uint64_t rtt_time) +{ + if (length < 1) { + return -1; + } + + if (data[0] != PACKET_ID_REQUEST) { + return -1; + } + + if (length == 1) { + return 0; + } + + ++data; + --length; + + uint32_t i, n = 1; + uint32_t requested = 0; + + uint64_t temp_time = current_time_monotonic(); + uint64_t l_sent_time = ~0; + + for (i = send_array->buffer_start; i != send_array->buffer_end; ++i) { + if (length == 0) { + break; + } + + uint32_t num = i % CRYPTO_PACKET_BUFFER_SIZE; + + if (n == data[0]) { + if (send_array->buffer[num]) { + uint64_t sent_time = send_array->buffer[num]->sent_time; + + if ((sent_time + rtt_time) < temp_time) { + send_array->buffer[num]->sent_time = 0; + } + } + + ++data; + --length; + n = 0; + ++requested; + } else { + if (send_array->buffer[num]) { + uint64_t sent_time = send_array->buffer[num]->sent_time; + + if (l_sent_time < sent_time) { + l_sent_time = sent_time; + } + + free(send_array->buffer[num]); + send_array->buffer[num] = NULL; + } + } + + if (n == 255) { + n = 1; + + if (data[0] != 0) { + return -1; + } + + ++data; + --length; + } else { + ++n; + } + } + + if (*latest_send_time < l_sent_time) { + *latest_send_time = l_sent_time; + } + + return requested; +} + +/** END: Array Related functions **/ + +#define MAX_DATA_DATA_PACKET_SIZE (MAX_CRYPTO_PACKET_SIZE - (1 + sizeof(uint16_t) + CRYPTO_MAC_SIZE)) + +/* Creates and sends a data packet to the peer using the fastest route. + * + * return -1 on failure. + * return 0 on success. + */ +static int send_data_packet(Net_Crypto *c, int crypt_connection_id, const uint8_t *data, uint16_t length) +{ + if (length == 0 || length + (1 + sizeof(uint16_t) + CRYPTO_MAC_SIZE) > MAX_CRYPTO_PACKET_SIZE) { + return -1; + } + + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) { + return -1; + } + + pthread_mutex_lock(&conn->mutex); + VLA(uint8_t, packet, 1 + sizeof(uint16_t) + length + CRYPTO_MAC_SIZE); + packet[0] = NET_PACKET_CRYPTO_DATA; + memcpy(packet + 1, conn->sent_nonce + (CRYPTO_NONCE_SIZE - sizeof(uint16_t)), sizeof(uint16_t)); + int len = encrypt_data_symmetric(conn->shared_key, conn->sent_nonce, data, length, packet + 1 + sizeof(uint16_t)); + + if (len + 1 + sizeof(uint16_t) != SIZEOF_VLA(packet)) { + pthread_mutex_unlock(&conn->mutex); + return -1; + } + + increment_nonce(conn->sent_nonce); + pthread_mutex_unlock(&conn->mutex); + + return send_packet_to(c, crypt_connection_id, packet, SIZEOF_VLA(packet)); +} + +/* Creates and sends a data packet with buffer_start and num to the peer using the fastest route. + * + * return -1 on failure. + * return 0 on success. + */ +static int send_data_packet_helper(Net_Crypto *c, int crypt_connection_id, uint32_t buffer_start, uint32_t num, + const uint8_t *data, uint16_t length) +{ + if (length == 0 || length > MAX_CRYPTO_DATA_SIZE) { + return -1; + } + + num = net_htonl(num); + buffer_start = net_htonl(buffer_start); + uint16_t padding_length = (MAX_CRYPTO_DATA_SIZE - length) % CRYPTO_MAX_PADDING; + VLA(uint8_t, packet, sizeof(uint32_t) + sizeof(uint32_t) + padding_length + length); + memcpy(packet, &buffer_start, sizeof(uint32_t)); + memcpy(packet + sizeof(uint32_t), &num, sizeof(uint32_t)); + memset(packet + (sizeof(uint32_t) * 2), PACKET_ID_PADDING, padding_length); + memcpy(packet + (sizeof(uint32_t) * 2) + padding_length, data, length); + + return send_data_packet(c, crypt_connection_id, packet, SIZEOF_VLA(packet)); +} + +static int reset_max_speed_reached(Net_Crypto *c, int crypt_connection_id) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) { + return -1; + } + + /* If last packet send failed, try to send packet again. + If sending it fails we won't be able to send the new packet. */ + if (conn->maximum_speed_reached) { + Packet_Data *dt = NULL; + uint32_t packet_num = conn->send_array.buffer_end - 1; + int ret = get_data_pointer(&conn->send_array, &dt, packet_num); + + uint8_t send_failed = 0; + + if (ret == 1) { + if (!dt->sent_time) { + if (send_data_packet_helper(c, crypt_connection_id, conn->recv_array.buffer_start, packet_num, dt->data, + dt->length) != 0) { + send_failed = 1; + } else { + dt->sent_time = current_time_monotonic(); + } + } + } + + if (!send_failed) { + conn->maximum_speed_reached = 0; + } else { + return -1; + } + } + + return 0; +} + +/* return -1 if data could not be put in packet queue. + * return positive packet number if data was put into the queue. + */ +static int64_t send_lossless_packet(Net_Crypto *c, int crypt_connection_id, const uint8_t *data, uint16_t length, + uint8_t congestion_control) +{ + if (length == 0 || length > MAX_CRYPTO_DATA_SIZE) { + return -1; + } + + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) { + return -1; + } + + /* If last packet send failed, try to send packet again. + If sending it fails we won't be able to send the new packet. */ + reset_max_speed_reached(c, crypt_connection_id); + + if (conn->maximum_speed_reached && congestion_control) { + return -1; + } + + Packet_Data dt; + dt.sent_time = 0; + dt.length = length; + memcpy(dt.data, data, length); + pthread_mutex_lock(&conn->mutex); + int64_t packet_num = add_data_end_of_buffer(&conn->send_array, &dt); + pthread_mutex_unlock(&conn->mutex); + + if (packet_num == -1) { + return -1; + } + + if (!congestion_control && conn->maximum_speed_reached) { + return packet_num; + } + + if (send_data_packet_helper(c, crypt_connection_id, conn->recv_array.buffer_start, packet_num, data, length) == 0) { + Packet_Data *dt1 = NULL; + + if (get_data_pointer(&conn->send_array, &dt1, packet_num) == 1) { + dt1->sent_time = current_time_monotonic(); + } + } else { + conn->maximum_speed_reached = 1; + LOGGER_ERROR(c->log, "send_data_packet failed\n"); + } + + return packet_num; +} + +/* Get the lowest 2 bytes from the nonce and convert + * them to host byte format before returning them. + */ +static uint16_t get_nonce_uint16(const uint8_t *nonce) +{ + uint16_t num; + memcpy(&num, nonce + (CRYPTO_NONCE_SIZE - sizeof(uint16_t)), sizeof(uint16_t)); + return net_ntohs(num); +} + +#define DATA_NUM_THRESHOLD 21845 + +/* Handle a data packet. + * Decrypt packet of length and put it into data. + * data must be at least MAX_DATA_DATA_PACKET_SIZE big. + * + * return -1 on failure. + * return length of data on success. + */ +static int handle_data_packet(const Net_Crypto *c, int crypt_connection_id, uint8_t *data, const uint8_t *packet, + uint16_t length) +{ + if (length <= (1 + sizeof(uint16_t) + CRYPTO_MAC_SIZE) || length > MAX_CRYPTO_PACKET_SIZE) { + return -1; + } + + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) { + return -1; + } + + uint8_t nonce[CRYPTO_NONCE_SIZE]; + memcpy(nonce, conn->recv_nonce, CRYPTO_NONCE_SIZE); + uint16_t num_cur_nonce = get_nonce_uint16(nonce); + uint16_t num; + memcpy(&num, packet + 1, sizeof(uint16_t)); + num = net_ntohs(num); + uint16_t diff = num - num_cur_nonce; + increment_nonce_number(nonce, diff); + int len = decrypt_data_symmetric(conn->shared_key, nonce, packet + 1 + sizeof(uint16_t), + length - (1 + sizeof(uint16_t)), data); + + if ((unsigned int)len != length - (1 + sizeof(uint16_t) + CRYPTO_MAC_SIZE)) { + return -1; + } + + if (diff > DATA_NUM_THRESHOLD * 2) { + increment_nonce_number(conn->recv_nonce, DATA_NUM_THRESHOLD); + } + + return len; +} + +/* Send a request packet. + * + * return -1 on failure. + * return 0 on success. + */ +static int send_request_packet(Net_Crypto *c, int crypt_connection_id) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) { + return -1; + } + + uint8_t data[MAX_CRYPTO_DATA_SIZE]; + int len = generate_request_packet(data, sizeof(data), &conn->recv_array); + + if (len == -1) { + return -1; + } + + return send_data_packet_helper(c, crypt_connection_id, conn->recv_array.buffer_start, conn->send_array.buffer_end, data, + len); +} + +/* Send up to max num previously requested data packets. + * + * return -1 on failure. + * return number of packets sent on success. + */ +static int send_requested_packets(Net_Crypto *c, int crypt_connection_id, uint32_t max_num) +{ + if (max_num == 0) { + return -1; + } + + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) { + return -1; + } + + uint64_t temp_time = current_time_monotonic(); + uint32_t i, num_sent = 0, array_size = num_packets_array(&conn->send_array); + + for (i = 0; i < array_size; ++i) { + Packet_Data *dt; + uint32_t packet_num = (i + conn->send_array.buffer_start); + int ret = get_data_pointer(&conn->send_array, &dt, packet_num); + + if (ret == -1) { + return -1; + } + + if (ret == 0) { + continue; + } + + if (dt->sent_time) { + continue; + } + + if (send_data_packet_helper(c, crypt_connection_id, conn->recv_array.buffer_start, packet_num, dt->data, + dt->length) == 0) { + dt->sent_time = temp_time; + ++num_sent; + } + + if (num_sent >= max_num) { + break; + } + } + + return num_sent; +} + + +/* Add a new temp packet to send repeatedly. + * + * return -1 on failure. + * return 0 on success. + */ +static int new_temp_packet(const Net_Crypto *c, int crypt_connection_id, const uint8_t *packet, uint16_t length) +{ + if (length == 0 || length > MAX_CRYPTO_PACKET_SIZE) { + return -1; + } + + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) { + return -1; + } + + uint8_t *temp_packet = (uint8_t *)malloc(length); + + if (temp_packet == 0) { + return -1; + } + + if (conn->temp_packet) { + free(conn->temp_packet); + } + + memcpy(temp_packet, packet, length); + conn->temp_packet = temp_packet; + conn->temp_packet_length = length; + conn->temp_packet_sent_time = 0; + conn->temp_packet_num_sent = 0; + return 0; +} + +/* Clear the temp packet. + * + * return -1 on failure. + * return 0 on success. + */ +static int clear_temp_packet(const Net_Crypto *c, int crypt_connection_id) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) { + return -1; + } + + if (conn->temp_packet) { + free(conn->temp_packet); + } + + conn->temp_packet = 0; + conn->temp_packet_length = 0; + conn->temp_packet_sent_time = 0; + conn->temp_packet_num_sent = 0; + return 0; +} + + +/* Send the temp packet. + * + * return -1 on failure. + * return 0 on success. + */ +static int send_temp_packet(Net_Crypto *c, int crypt_connection_id) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) { + return -1; + } + + if (!conn->temp_packet) { + return -1; + } + + if (send_packet_to(c, crypt_connection_id, conn->temp_packet, conn->temp_packet_length) != 0) { + return -1; + } + + conn->temp_packet_sent_time = current_time_monotonic(); + ++conn->temp_packet_num_sent; + return 0; +} + +/* Create a handshake packet and set it as a temp packet. + * cookie must be COOKIE_LENGTH. + * + * return -1 on failure. + * return 0 on success. + */ +static int create_send_handshake(Net_Crypto *c, int crypt_connection_id, const uint8_t *cookie, + const uint8_t *dht_public_key) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) { + return -1; + } + + uint8_t handshake_packet[HANDSHAKE_PACKET_LENGTH]; + + if (create_crypto_handshake(c, handshake_packet, cookie, conn->sent_nonce, conn->sessionpublic_key, + conn->public_key, dht_public_key) != sizeof(handshake_packet)) { + return -1; + } + + if (new_temp_packet(c, crypt_connection_id, handshake_packet, sizeof(handshake_packet)) != 0) { + return -1; + } + + send_temp_packet(c, crypt_connection_id); + return 0; +} + +/* Send a kill packet. + * + * return -1 on failure. + * return 0 on success. + */ +static int send_kill_packet(Net_Crypto *c, int crypt_connection_id) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) { + return -1; + } + + uint8_t kill_packet = PACKET_ID_KILL; + return send_data_packet_helper(c, crypt_connection_id, conn->recv_array.buffer_start, conn->send_array.buffer_end, + &kill_packet, sizeof(kill_packet)); +} + +static void connection_kill(Net_Crypto *c, int crypt_connection_id, void *userdata) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) { + return; + } + + if (conn->connection_status_callback) { + conn->connection_status_callback(conn->connection_status_callback_object, conn->connection_status_callback_id, 0, + userdata); + } + + crypto_kill(c, crypt_connection_id); +} + +/* Handle a received data packet. + * + * return -1 on failure. + * return 0 on success. + */ +static int handle_data_packet_core(Net_Crypto *c, int crypt_connection_id, const uint8_t *packet, uint16_t length, + bool udp, void *userdata) +{ + if (length > MAX_CRYPTO_PACKET_SIZE || length <= CRYPTO_DATA_PACKET_MIN_SIZE) { + return -1; + } + + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) { + return -1; + } + + uint8_t data[MAX_DATA_DATA_PACKET_SIZE]; + int len = handle_data_packet(c, crypt_connection_id, data, packet, length); + + if (len <= (int)(sizeof(uint32_t) * 2)) { + return -1; + } + + uint32_t buffer_start, num; + memcpy(&buffer_start, data, sizeof(uint32_t)); + memcpy(&num, data + sizeof(uint32_t), sizeof(uint32_t)); + buffer_start = net_ntohl(buffer_start); + num = net_ntohl(num); + + uint64_t rtt_calc_time = 0; + + if (buffer_start != conn->send_array.buffer_start) { + Packet_Data *packet_time; + + if (get_data_pointer(&conn->send_array, &packet_time, conn->send_array.buffer_start) == 1) { + rtt_calc_time = packet_time->sent_time; + } + + if (clear_buffer_until(&conn->send_array, buffer_start) != 0) { + return -1; + } + } + + uint8_t *real_data = data + (sizeof(uint32_t) * 2); + uint16_t real_length = len - (sizeof(uint32_t) * 2); + + while (real_data[0] == PACKET_ID_PADDING) { /* Remove Padding */ + ++real_data; + --real_length; + + if (real_length == 0) { + return -1; + } + } + + if (real_data[0] == PACKET_ID_KILL) { + connection_kill(c, crypt_connection_id, userdata); + return 0; + } + + if (conn->status == CRYPTO_CONN_NOT_CONFIRMED) { + clear_temp_packet(c, crypt_connection_id); + conn->status = CRYPTO_CONN_ESTABLISHED; + + if (conn->connection_status_callback) { + conn->connection_status_callback(conn->connection_status_callback_object, conn->connection_status_callback_id, 1, + userdata); + } + } + + if (real_data[0] == PACKET_ID_REQUEST) { + uint64_t rtt_time; + + if (udp) { + rtt_time = conn->rtt_time; + } else { + rtt_time = DEFAULT_TCP_PING_CONNECTION; + } + + int requested = handle_request_packet(&conn->send_array, real_data, real_length, &rtt_calc_time, rtt_time); + + if (requested == -1) { + return -1; + } + + // else { /* TODO(irungentoo): ? */ } + + set_buffer_end(&conn->recv_array, num); + } else if (real_data[0] >= CRYPTO_RESERVED_PACKETS && real_data[0] < PACKET_ID_LOSSY_RANGE_START) { + Packet_Data dt; + dt.length = real_length; + memcpy(dt.data, real_data, real_length); + + if (add_data_to_buffer(&conn->recv_array, num, &dt) != 0) { + return -1; + } + + while (1) { + pthread_mutex_lock(&conn->mutex); + int ret = read_data_beg_buffer(&conn->recv_array, &dt); + pthread_mutex_unlock(&conn->mutex); + + if (ret == -1) { + break; + } + + if (conn->connection_data_callback) { + conn->connection_data_callback(conn->connection_data_callback_object, conn->connection_data_callback_id, dt.data, + dt.length, userdata); + } + + /* conn might get killed in callback. */ + conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) { + return -1; + } + } + + /* Packet counter. */ + ++conn->packet_counter; + } else if (real_data[0] >= PACKET_ID_LOSSY_RANGE_START && + real_data[0] < (PACKET_ID_LOSSY_RANGE_START + PACKET_ID_LOSSY_RANGE_SIZE)) { + + set_buffer_end(&conn->recv_array, num); + + if (conn->connection_lossy_data_callback) { + conn->connection_lossy_data_callback(conn->connection_lossy_data_callback_object, + conn->connection_lossy_data_callback_id, real_data, real_length, userdata); + } + } else { + return -1; + } + + if (rtt_calc_time != 0) { + uint64_t rtt_time = current_time_monotonic() - rtt_calc_time; + + if (rtt_time < conn->rtt_time) { + conn->rtt_time = rtt_time; + } + } + + return 0; +} + +/* Handle a packet that was received for the connection. + * + * return -1 on failure. + * return 0 on success. + */ +static int handle_packet_connection(Net_Crypto *c, int crypt_connection_id, const uint8_t *packet, uint16_t length, + bool udp, void *userdata) +{ + if (length == 0 || length > MAX_CRYPTO_PACKET_SIZE) { + return -1; + } + + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) { + return -1; + } + + switch (packet[0]) { + case NET_PACKET_COOKIE_RESPONSE: { + if (conn->status != CRYPTO_CONN_COOKIE_REQUESTING) { + return -1; + } + + uint8_t cookie[COOKIE_LENGTH]; + uint64_t number; + + if (handle_cookie_response(cookie, &number, packet, length, conn->shared_key) != sizeof(cookie)) { + return -1; + } + + if (number != conn->cookie_request_number) { + return -1; + } + + if (create_send_handshake(c, crypt_connection_id, cookie, conn->dht_public_key) != 0) { + return -1; + } + + conn->status = CRYPTO_CONN_HANDSHAKE_SENT; + return 0; + } + + case NET_PACKET_CRYPTO_HS: { + if (conn->status == CRYPTO_CONN_COOKIE_REQUESTING || conn->status == CRYPTO_CONN_HANDSHAKE_SENT + || conn->status == CRYPTO_CONN_NOT_CONFIRMED) { + uint8_t peer_real_pk[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t dht_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t cookie[COOKIE_LENGTH]; + + if (handle_crypto_handshake(c, conn->recv_nonce, conn->peersessionpublic_key, peer_real_pk, dht_public_key, cookie, + packet, length, conn->public_key) != 0) { + return -1; + } + + if (public_key_cmp(dht_public_key, conn->dht_public_key) == 0) { + encrypt_precompute(conn->peersessionpublic_key, conn->sessionsecret_key, conn->shared_key); + + if (conn->status == CRYPTO_CONN_COOKIE_REQUESTING) { + if (create_send_handshake(c, crypt_connection_id, cookie, dht_public_key) != 0) { + return -1; + } + } + + conn->status = CRYPTO_CONN_NOT_CONFIRMED; + } else { + if (conn->dht_pk_callback) { + conn->dht_pk_callback(conn->dht_pk_callback_object, conn->dht_pk_callback_number, dht_public_key, userdata); + } + } + } else { + return -1; + } + + return 0; + } + + case NET_PACKET_CRYPTO_DATA: { + if (conn->status == CRYPTO_CONN_NOT_CONFIRMED || conn->status == CRYPTO_CONN_ESTABLISHED) { + return handle_data_packet_core(c, crypt_connection_id, packet, length, udp, userdata); + } + + return -1; + } + + default: { + return -1; + } + } +} + +/* Set the size of the friend list to numfriends. + * + * return -1 if realloc fails. + * return 0 if it succeeds. + */ +static int realloc_cryptoconnection(Net_Crypto *c, uint32_t num) +{ + if (num == 0) { + free(c->crypto_connections); + c->crypto_connections = NULL; + return 0; + } + + Crypto_Connection *newcrypto_connections = (Crypto_Connection *)realloc(c->crypto_connections, + num * sizeof(Crypto_Connection)); + + if (newcrypto_connections == NULL) { + return -1; + } + + c->crypto_connections = newcrypto_connections; + return 0; +} + + +/* Create a new empty crypto connection. + * + * return -1 on failure. + * return connection id on success. + */ +static int create_crypto_connection(Net_Crypto *c) +{ + uint32_t i; + + for (i = 0; i < c->crypto_connections_length; ++i) { + if (c->crypto_connections[i].status == CRYPTO_CONN_NO_CONNECTION) { + return i; + } + } + + while (1) { /* TODO(irungentoo): is this really the best way to do this? */ + pthread_mutex_lock(&c->connections_mutex); + + if (!c->connection_use_counter) { + break; + } + + pthread_mutex_unlock(&c->connections_mutex); + } + + int id = -1; + + if (realloc_cryptoconnection(c, c->crypto_connections_length + 1) == 0) { + id = c->crypto_connections_length; + ++c->crypto_connections_length; + memset(&(c->crypto_connections[id]), 0, sizeof(Crypto_Connection)); + // Memsetting float/double to 0 is non-portable, so we explicitly set them to 0 + c->crypto_connections[id].packet_recv_rate = 0; + c->crypto_connections[id].packet_send_rate = 0; + c->crypto_connections[id].last_packets_left_rem = 0; + c->crypto_connections[id].packet_send_rate_requested = 0; + c->crypto_connections[id].last_packets_left_requested_rem = 0; + + if (pthread_mutex_init(&c->crypto_connections[id].mutex, NULL) != 0) { + pthread_mutex_unlock(&c->connections_mutex); + return -1; + } + } + + pthread_mutex_unlock(&c->connections_mutex); + return id; +} + +/* Wipe a crypto connection. + * + * return -1 on failure. + * return 0 on success. + */ +static int wipe_crypto_connection(Net_Crypto *c, int crypt_connection_id) +{ + if (crypt_connection_id_not_valid(c, crypt_connection_id)) { + return -1; + } + + uint32_t i; + + /* Keep mutex, only destroy it when connection is realloced out. */ + pthread_mutex_t mutex = c->crypto_connections[crypt_connection_id].mutex; + crypto_memzero(&(c->crypto_connections[crypt_connection_id]), sizeof(Crypto_Connection)); + c->crypto_connections[crypt_connection_id].mutex = mutex; + + for (i = c->crypto_connections_length; i != 0; --i) { + if (c->crypto_connections[i - 1].status == CRYPTO_CONN_NO_CONNECTION) { + pthread_mutex_destroy(&c->crypto_connections[i - 1].mutex); + } else { + break; + } + } + + if (c->crypto_connections_length != i) { + c->crypto_connections_length = i; + realloc_cryptoconnection(c, c->crypto_connections_length); + } + + return 0; +} + +/* Get crypto connection id from public key of peer. + * + * return -1 if there are no connections like we are looking for. + * return id if it found it. + */ +static int getcryptconnection_id(const Net_Crypto *c, const uint8_t *public_key) +{ + uint32_t i; + + for (i = 0; i < c->crypto_connections_length; ++i) { + if (c->crypto_connections[i].status != CRYPTO_CONN_NO_CONNECTION) { + if (public_key_cmp(public_key, c->crypto_connections[i].public_key) == 0) { + return i; + } + } + } + + return -1; +} + +/* Add a source to the crypto connection. + * This is to be used only when we have received a packet from that source. + * + * return -1 on failure. + * return positive number on success. + * 0 if source was a direct UDP connection. + */ +static int crypto_connection_add_source(Net_Crypto *c, int crypt_connection_id, IP_Port source) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) { + return -1; + } + + if (source.ip.family == TOX_AF_INET || source.ip.family == TOX_AF_INET6) { + if (add_ip_port_connection(c, crypt_connection_id, source) != 0) { + return -1; + } + + if (source.ip.family == TOX_AF_INET) { + conn->direct_lastrecv_timev4 = unix_time(); + } else { + conn->direct_lastrecv_timev6 = unix_time(); + } + + return 0; + } + + if (source.ip.family == TCP_FAMILY) { + if (add_tcp_number_relay_connection(c->tcp_c, conn->connection_number_tcp, source.ip.ip6.uint32[0]) == 0) { + return 1; + } + } + + return -1; +} + + +/* Set function to be called when someone requests a new connection to us. + * + * The set function should return -1 on failure and 0 on success. + * + * n_c is only valid for the duration of the function call. + */ +void new_connection_handler(Net_Crypto *c, int (*new_connection_callback)(void *object, New_Connection *n_c), + void *object) +{ + c->new_connection_callback = new_connection_callback; + c->new_connection_callback_object = object; +} + +/* Handle a handshake packet by someone who wants to initiate a new connection with us. + * This calls the callback set by new_connection_handler() if the handshake is ok. + * + * return -1 on failure. + * return 0 on success. + */ +static int handle_new_connection_handshake(Net_Crypto *c, IP_Port source, const uint8_t *data, uint16_t length, + void *userdata) +{ + New_Connection n_c; + n_c.cookie = (uint8_t *)malloc(COOKIE_LENGTH); + + if (n_c.cookie == NULL) { + return -1; + } + + n_c.source = source; + n_c.cookie_length = COOKIE_LENGTH; + + if (handle_crypto_handshake(c, n_c.recv_nonce, n_c.peersessionpublic_key, n_c.public_key, n_c.dht_public_key, + n_c.cookie, data, length, 0) != 0) { + free(n_c.cookie); + return -1; + } + + int crypt_connection_id = getcryptconnection_id(c, n_c.public_key); + + if (crypt_connection_id != -1) { + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (public_key_cmp(n_c.dht_public_key, conn->dht_public_key) != 0) { + connection_kill(c, crypt_connection_id, userdata); + } else { + int ret = -1; + + if (conn && (conn->status == CRYPTO_CONN_COOKIE_REQUESTING || conn->status == CRYPTO_CONN_HANDSHAKE_SENT)) { + memcpy(conn->recv_nonce, n_c.recv_nonce, CRYPTO_NONCE_SIZE); + memcpy(conn->peersessionpublic_key, n_c.peersessionpublic_key, CRYPTO_PUBLIC_KEY_SIZE); + encrypt_precompute(conn->peersessionpublic_key, conn->sessionsecret_key, conn->shared_key); + + crypto_connection_add_source(c, crypt_connection_id, source); + + if (create_send_handshake(c, crypt_connection_id, n_c.cookie, n_c.dht_public_key) == 0) { + conn->status = CRYPTO_CONN_NOT_CONFIRMED; + ret = 0; + } + } + + free(n_c.cookie); + return ret; + } + } + + int ret = c->new_connection_callback(c->new_connection_callback_object, &n_c); + free(n_c.cookie); + return ret; +} + +/* Accept a crypto connection. + * + * return -1 on failure. + * return connection id on success. + */ +int accept_crypto_connection(Net_Crypto *c, New_Connection *n_c) +{ + if (getcryptconnection_id(c, n_c->public_key) != -1) { + return -1; + } + + int crypt_connection_id = create_crypto_connection(c); + + if (crypt_connection_id == -1) { + return -1; + } + + Crypto_Connection *conn = &c->crypto_connections[crypt_connection_id]; + + if (n_c->cookie_length != COOKIE_LENGTH) { + return -1; + } + + pthread_mutex_lock(&c->tcp_mutex); + int connection_number_tcp = new_tcp_connection_to(c->tcp_c, n_c->dht_public_key, crypt_connection_id); + pthread_mutex_unlock(&c->tcp_mutex); + + if (connection_number_tcp == -1) { + return -1; + } + + conn->connection_number_tcp = connection_number_tcp; + memcpy(conn->public_key, n_c->public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(conn->recv_nonce, n_c->recv_nonce, CRYPTO_NONCE_SIZE); + memcpy(conn->peersessionpublic_key, n_c->peersessionpublic_key, CRYPTO_PUBLIC_KEY_SIZE); + random_nonce(conn->sent_nonce); + crypto_new_keypair(conn->sessionpublic_key, conn->sessionsecret_key); + encrypt_precompute(conn->peersessionpublic_key, conn->sessionsecret_key, conn->shared_key); + conn->status = CRYPTO_CONN_NOT_CONFIRMED; + + if (create_send_handshake(c, crypt_connection_id, n_c->cookie, n_c->dht_public_key) != 0) { + pthread_mutex_lock(&c->tcp_mutex); + kill_tcp_connection_to(c->tcp_c, conn->connection_number_tcp); + pthread_mutex_unlock(&c->tcp_mutex); + conn->status = CRYPTO_CONN_NO_CONNECTION; + return -1; + } + + memcpy(conn->dht_public_key, n_c->dht_public_key, CRYPTO_PUBLIC_KEY_SIZE); + conn->packet_send_rate = CRYPTO_PACKET_MIN_RATE; + conn->packet_send_rate_requested = CRYPTO_PACKET_MIN_RATE; + conn->packets_left = CRYPTO_MIN_QUEUE_LENGTH; + conn->rtt_time = DEFAULT_PING_CONNECTION; + crypto_connection_add_source(c, crypt_connection_id, n_c->source); + return crypt_connection_id; +} + +/* Create a crypto connection. + * If one to that real public key already exists, return it. + * + * return -1 on failure. + * return connection id on success. + */ +int new_crypto_connection(Net_Crypto *c, const uint8_t *real_public_key, const uint8_t *dht_public_key) +{ + int crypt_connection_id = getcryptconnection_id(c, real_public_key); + + if (crypt_connection_id != -1) { + return crypt_connection_id; + } + + crypt_connection_id = create_crypto_connection(c); + + if (crypt_connection_id == -1) { + return -1; + } + + Crypto_Connection *conn = &c->crypto_connections[crypt_connection_id]; + + if (conn == 0) { + return -1; + } + + pthread_mutex_lock(&c->tcp_mutex); + int connection_number_tcp = new_tcp_connection_to(c->tcp_c, dht_public_key, crypt_connection_id); + pthread_mutex_unlock(&c->tcp_mutex); + + if (connection_number_tcp == -1) { + return -1; + } + + conn->connection_number_tcp = connection_number_tcp; + memcpy(conn->public_key, real_public_key, CRYPTO_PUBLIC_KEY_SIZE); + random_nonce(conn->sent_nonce); + crypto_new_keypair(conn->sessionpublic_key, conn->sessionsecret_key); + conn->status = CRYPTO_CONN_COOKIE_REQUESTING; + conn->packet_send_rate = CRYPTO_PACKET_MIN_RATE; + conn->packet_send_rate_requested = CRYPTO_PACKET_MIN_RATE; + conn->packets_left = CRYPTO_MIN_QUEUE_LENGTH; + conn->rtt_time = DEFAULT_PING_CONNECTION; + memcpy(conn->dht_public_key, dht_public_key, CRYPTO_PUBLIC_KEY_SIZE); + + conn->cookie_request_number = random_64b(); + uint8_t cookie_request[COOKIE_REQUEST_LENGTH]; + + if (create_cookie_request(c, cookie_request, conn->dht_public_key, conn->cookie_request_number, + conn->shared_key) != sizeof(cookie_request) + || new_temp_packet(c, crypt_connection_id, cookie_request, sizeof(cookie_request)) != 0) { + pthread_mutex_lock(&c->tcp_mutex); + kill_tcp_connection_to(c->tcp_c, conn->connection_number_tcp); + pthread_mutex_unlock(&c->tcp_mutex); + conn->status = CRYPTO_CONN_NO_CONNECTION; + return -1; + } + + return crypt_connection_id; +} + +/* Set the direct ip of the crypto connection. + * + * Connected is 0 if we are not sure we are connected to that person, 1 if we are sure. + * + * return -1 on failure. + * return 0 on success. + */ +int set_direct_ip_port(Net_Crypto *c, int crypt_connection_id, IP_Port ip_port, bool connected) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) { + return -1; + } + + if (add_ip_port_connection(c, crypt_connection_id, ip_port) == 0) { + if (connected) { + if (ip_port.ip.family == TOX_AF_INET) { + conn->direct_lastrecv_timev4 = unix_time(); + } else { + conn->direct_lastrecv_timev6 = unix_time(); + } + } else { + if (ip_port.ip.family == TOX_AF_INET) { + conn->direct_lastrecv_timev4 = 0; + } else { + conn->direct_lastrecv_timev6 = 0; + } + } + + return 0; + } + + return -1; +} + + +static int tcp_data_callback(void *object, int id, const uint8_t *data, uint16_t length, void *userdata) +{ + if (length == 0 || length > MAX_CRYPTO_PACKET_SIZE) { + return -1; + } + + Net_Crypto *c = (Net_Crypto *)object; + + Crypto_Connection *conn = get_crypto_connection(c, id); + + if (conn == 0) { + return -1; + } + + if (data[0] == NET_PACKET_COOKIE_REQUEST) { + return tcp_handle_cookie_request(c, conn->connection_number_tcp, data, length); + } + + // This unlocks the mutex that at this point is locked by do_tcp before + // calling do_tcp_connections. + pthread_mutex_unlock(&c->tcp_mutex); + int ret = handle_packet_connection(c, id, data, length, 0, userdata); + pthread_mutex_lock(&c->tcp_mutex); + + if (ret != 0) { + return -1; + } + + // TODO(irungentoo): detect and kill bad TCP connections. + return 0; +} + +static int tcp_oob_callback(void *object, const uint8_t *public_key, unsigned int tcp_connections_number, + const uint8_t *data, uint16_t length, void *userdata) +{ + if (length == 0 || length > MAX_CRYPTO_PACKET_SIZE) { + return -1; + } + + Net_Crypto *c = (Net_Crypto *)object; + + if (data[0] == NET_PACKET_COOKIE_REQUEST) { + return tcp_oob_handle_cookie_request(c, tcp_connections_number, public_key, data, length); + } + + if (data[0] == NET_PACKET_CRYPTO_HS) { + IP_Port source; + source.port = 0; + source.ip.family = TCP_FAMILY; + source.ip.ip6.uint32[0] = tcp_connections_number; + + if (handle_new_connection_handshake(c, source, data, length, userdata) != 0) { + return -1; + } + + return 0; + } + + return -1; +} + +/* Add a tcp relay, associating it to a crypt_connection_id. + * + * return 0 if it was added. + * return -1 if it wasn't. + */ +int add_tcp_relay_peer(Net_Crypto *c, int crypt_connection_id, IP_Port ip_port, const uint8_t *public_key) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) { + return -1; + } + + pthread_mutex_lock(&c->tcp_mutex); + int ret = add_tcp_relay_connection(c->tcp_c, conn->connection_number_tcp, ip_port, public_key); + pthread_mutex_unlock(&c->tcp_mutex); + return ret; +} + +/* Add a tcp relay to the array. + * + * return 0 if it was added. + * return -1 if it wasn't. + */ +int add_tcp_relay(Net_Crypto *c, IP_Port ip_port, const uint8_t *public_key) +{ + pthread_mutex_lock(&c->tcp_mutex); + int ret = add_tcp_relay_global(c->tcp_c, ip_port, public_key); + pthread_mutex_unlock(&c->tcp_mutex); + return ret; +} + +/* Return a random TCP connection number for use in send_tcp_onion_request. + * + * TODO(irungentoo): This number is just the index of an array that the elements can + * change without warning. + * + * return TCP connection number on success. + * return -1 on failure. + */ +int get_random_tcp_con_number(Net_Crypto *c) +{ + pthread_mutex_lock(&c->tcp_mutex); + int ret = get_random_tcp_onion_conn_number(c->tcp_c); + pthread_mutex_unlock(&c->tcp_mutex); + + return ret; +} + +/* Send an onion packet via the TCP relay corresponding to tcp_connections_number. + * + * return 0 on success. + * return -1 on failure. + */ +int send_tcp_onion_request(Net_Crypto *c, unsigned int tcp_connections_number, const uint8_t *data, uint16_t length) +{ + pthread_mutex_lock(&c->tcp_mutex); + int ret = tcp_send_onion_request(c->tcp_c, tcp_connections_number, data, length); + pthread_mutex_unlock(&c->tcp_mutex); + + return ret; +} + +/* Copy a maximum of num TCP relays we are connected to to tcp_relays. + * NOTE that the family of the copied ip ports will be set to TCP_INET or TCP_INET6. + * + * return number of relays copied to tcp_relays on success. + * return 0 on failure. + */ +unsigned int copy_connected_tcp_relays(Net_Crypto *c, Node_format *tcp_relays, uint16_t num) +{ + if (num == 0) { + return 0; + } + + pthread_mutex_lock(&c->tcp_mutex); + unsigned int ret = tcp_copy_connected_relays(c->tcp_c, tcp_relays, num); + pthread_mutex_unlock(&c->tcp_mutex); + + return ret; +} + +static void do_tcp(Net_Crypto *c, void *userdata) +{ + pthread_mutex_lock(&c->tcp_mutex); + do_tcp_connections(c->tcp_c, userdata); + pthread_mutex_unlock(&c->tcp_mutex); + + uint32_t i; + + for (i = 0; i < c->crypto_connections_length; ++i) { + Crypto_Connection *conn = get_crypto_connection(c, i); + + if (conn == 0) { + return; + } + + if (conn->status == CRYPTO_CONN_ESTABLISHED) { + bool direct_connected = 0; + crypto_connection_status(c, i, &direct_connected, NULL); + + if (direct_connected) { + pthread_mutex_lock(&c->tcp_mutex); + set_tcp_connection_to_status(c->tcp_c, conn->connection_number_tcp, 0); + pthread_mutex_unlock(&c->tcp_mutex); + } else { + pthread_mutex_lock(&c->tcp_mutex); + set_tcp_connection_to_status(c->tcp_c, conn->connection_number_tcp, 1); + pthread_mutex_unlock(&c->tcp_mutex); + } + } + } +} + +/* Set function to be called when connection with crypt_connection_id goes connects/disconnects. + * + * The set function should return -1 on failure and 0 on success. + * Note that if this function is set, the connection will clear itself on disconnect. + * Object and id will be passed to this function untouched. + * status is 1 if the connection is going online, 0 if it is going offline. + * + * return -1 on failure. + * return 0 on success. + */ +int connection_status_handler(const Net_Crypto *c, int crypt_connection_id, + int (*connection_status_callback)(void *object, int id, uint8_t status, void *userdata), void *object, int id) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) { + return -1; + } + + conn->connection_status_callback = connection_status_callback; + conn->connection_status_callback_object = object; + conn->connection_status_callback_id = id; + return 0; +} + +/* Set function to be called when connection with crypt_connection_id receives a data packet of length. + * + * The set function should return -1 on failure and 0 on success. + * Object and id will be passed to this function untouched. + * + * return -1 on failure. + * return 0 on success. + */ +int connection_data_handler(const Net_Crypto *c, int crypt_connection_id, int (*connection_data_callback)(void *object, + int id, const uint8_t *data, uint16_t length, void *userdata), void *object, int id) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) { + return -1; + } + + conn->connection_data_callback = connection_data_callback; + conn->connection_data_callback_object = object; + conn->connection_data_callback_id = id; + return 0; +} + +/* Set function to be called when connection with crypt_connection_id receives a lossy data packet of length. + * + * The set function should return -1 on failure and 0 on success. + * Object and id will be passed to this function untouched. + * + * return -1 on failure. + * return 0 on success. + */ +int connection_lossy_data_handler(Net_Crypto *c, int crypt_connection_id, + int (*connection_lossy_data_callback)(void *object, int id, const uint8_t *data, uint16_t length, void *userdata), + void *object, int id) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) { + return -1; + } + + conn->connection_lossy_data_callback = connection_lossy_data_callback; + conn->connection_lossy_data_callback_object = object; + conn->connection_lossy_data_callback_id = id; + return 0; +} + + +/* Set the function for this friend that will be callbacked with object and number if + * the friend sends us a different dht public key than we have associated to him. + * + * If this function is called, the connection should be recreated with the new public key. + * + * object and number will be passed as argument to this function. + * + * return -1 on failure. + * return 0 on success. + */ +int nc_dht_pk_callback(Net_Crypto *c, int crypt_connection_id, void (*function)(void *data, int32_t number, + const uint8_t *dht_public_key, void *userdata), void *object, uint32_t number) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) { + return -1; + } + + conn->dht_pk_callback = function; + conn->dht_pk_callback_object = object; + conn->dht_pk_callback_number = number; + return 0; +} + +/* Get the crypto connection id from the ip_port. + * + * return -1 on failure. + * return connection id on success. + */ +static int crypto_id_ip_port(const Net_Crypto *c, IP_Port ip_port) +{ + return bs_list_find(&c->ip_port_list, (uint8_t *)&ip_port); +} + +#define CRYPTO_MIN_PACKET_SIZE (1 + sizeof(uint16_t) + CRYPTO_MAC_SIZE) + +/* Handle raw UDP packets coming directly from the socket. + * + * Handles: + * Cookie response packets. + * Crypto handshake packets. + * Crypto data packets. + * + */ +static int udp_handle_packet(void *object, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) +{ + if (length <= CRYPTO_MIN_PACKET_SIZE || length > MAX_CRYPTO_PACKET_SIZE) { + return 1; + } + + Net_Crypto *c = (Net_Crypto *)object; + int crypt_connection_id = crypto_id_ip_port(c, source); + + if (crypt_connection_id == -1) { + if (packet[0] != NET_PACKET_CRYPTO_HS) { + return 1; + } + + if (handle_new_connection_handshake(c, source, packet, length, userdata) != 0) { + return 1; + } + + return 0; + } + + if (handle_packet_connection(c, crypt_connection_id, packet, length, 1, userdata) != 0) { + return 1; + } + + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) { + return -1; + } + + pthread_mutex_lock(&conn->mutex); + + if (source.ip.family == TOX_AF_INET) { + conn->direct_lastrecv_timev4 = unix_time(); + } else { + conn->direct_lastrecv_timev6 = unix_time(); + } + + pthread_mutex_unlock(&conn->mutex); + return 0; +} + +/* The dT for the average packet receiving rate calculations. + Also used as the */ +#define PACKET_COUNTER_AVERAGE_INTERVAL 50 + +/* Ratio of recv queue size / recv packet rate (in seconds) times + * the number of ms between request packets to send at that ratio + */ +#define REQUEST_PACKETS_COMPARE_CONSTANT (0.125 * 100.0) + +/* Timeout for increasing speed after congestion event (in ms). */ +#define CONGESTION_EVENT_TIMEOUT 1000 + +/* If the send queue is SEND_QUEUE_RATIO times larger than the + * calculated link speed the packet send speed will be reduced + * by a value depending on this number. + */ +#define SEND_QUEUE_RATIO 2.0 + +static void send_crypto_packets(Net_Crypto *c) +{ + uint32_t i; + uint64_t temp_time = current_time_monotonic(); + double total_send_rate = 0; + uint32_t peak_request_packet_interval = ~0; + + for (i = 0; i < c->crypto_connections_length; ++i) { + Crypto_Connection *conn = get_crypto_connection(c, i); + + if (conn == 0) { + return; + } + + if (CRYPTO_SEND_PACKET_INTERVAL + conn->temp_packet_sent_time < temp_time) { + send_temp_packet(c, i); + } + + if ((conn->status == CRYPTO_CONN_NOT_CONFIRMED || conn->status == CRYPTO_CONN_ESTABLISHED) + && ((CRYPTO_SEND_PACKET_INTERVAL) + conn->last_request_packet_sent) < temp_time) { + if (send_request_packet(c, i) == 0) { + conn->last_request_packet_sent = temp_time; + } + } + + if (conn->status == CRYPTO_CONN_ESTABLISHED) { + if (conn->packet_recv_rate > CRYPTO_PACKET_MIN_RATE) { + double request_packet_interval = (REQUEST_PACKETS_COMPARE_CONSTANT / ((num_packets_array( + &conn->recv_array) + 1.0) / (conn->packet_recv_rate + 1.0))); + + double request_packet_interval2 = ((CRYPTO_PACKET_MIN_RATE / conn->packet_recv_rate) * + (double)CRYPTO_SEND_PACKET_INTERVAL) + (double)PACKET_COUNTER_AVERAGE_INTERVAL; + + if (request_packet_interval2 < request_packet_interval) { + request_packet_interval = request_packet_interval2; + } + + if (request_packet_interval < PACKET_COUNTER_AVERAGE_INTERVAL) { + request_packet_interval = PACKET_COUNTER_AVERAGE_INTERVAL; + } + + if (request_packet_interval > CRYPTO_SEND_PACKET_INTERVAL) { + request_packet_interval = CRYPTO_SEND_PACKET_INTERVAL; + } + + if (temp_time - conn->last_request_packet_sent > (uint64_t)request_packet_interval) { + if (send_request_packet(c, i) == 0) { + conn->last_request_packet_sent = temp_time; + } + } + + if (request_packet_interval < peak_request_packet_interval) { + peak_request_packet_interval = request_packet_interval; + } + } + + if ((PACKET_COUNTER_AVERAGE_INTERVAL + conn->packet_counter_set) < temp_time) { + + double dt = temp_time - conn->packet_counter_set; + + conn->packet_recv_rate = (double)conn->packet_counter / (dt / 1000.0); + conn->packet_counter = 0; + conn->packet_counter_set = temp_time; + + uint32_t packets_sent = conn->packets_sent; + conn->packets_sent = 0; + + uint32_t packets_resent = conn->packets_resent; + conn->packets_resent = 0; + + /* conjestion control + calculate a new value of conn->packet_send_rate based on some data + */ + + unsigned int pos = conn->last_sendqueue_counter % CONGESTION_QUEUE_ARRAY_SIZE; + conn->last_sendqueue_size[pos] = num_packets_array(&conn->send_array); + ++conn->last_sendqueue_counter; + + unsigned int j; + long signed int sum = 0; + sum = (long signed int)conn->last_sendqueue_size[(pos) % CONGESTION_QUEUE_ARRAY_SIZE] - + (long signed int)conn->last_sendqueue_size[(pos - (CONGESTION_QUEUE_ARRAY_SIZE - 1)) % CONGESTION_QUEUE_ARRAY_SIZE]; + + unsigned int n_p_pos = conn->last_sendqueue_counter % CONGESTION_LAST_SENT_ARRAY_SIZE; + conn->last_num_packets_sent[n_p_pos] = packets_sent; + conn->last_num_packets_resent[n_p_pos] = packets_resent; + + bool direct_connected = 0; + crypto_connection_status(c, i, &direct_connected, NULL); + + if (direct_connected && conn->last_tcp_sent + CONGESTION_EVENT_TIMEOUT > temp_time) { + /* When switching from TCP to UDP, don't change the packet send rate for CONGESTION_EVENT_TIMEOUT ms. */ + } else { + long signed int total_sent = 0, total_resent = 0; + + // TODO(irungentoo): use real delay + unsigned int delay = (unsigned int)((conn->rtt_time / PACKET_COUNTER_AVERAGE_INTERVAL) + 0.5); + unsigned int packets_set_rem_array = (CONGESTION_LAST_SENT_ARRAY_SIZE - CONGESTION_QUEUE_ARRAY_SIZE); + + if (delay > packets_set_rem_array) { + delay = packets_set_rem_array; + } + + for (j = 0; j < CONGESTION_QUEUE_ARRAY_SIZE; ++j) { + unsigned int ind = (j + (packets_set_rem_array - delay) + n_p_pos) % CONGESTION_LAST_SENT_ARRAY_SIZE; + total_sent += conn->last_num_packets_sent[ind]; + total_resent += conn->last_num_packets_resent[ind]; + } + + if (sum > 0) { + total_sent -= sum; + } else { + if (total_resent > -sum) { + total_resent = -sum; + } + } + + /* if queue is too big only allow resending packets. */ + uint32_t npackets = num_packets_array(&conn->send_array); + double min_speed = 1000.0 * (((double)(total_sent)) / ((double)(CONGESTION_QUEUE_ARRAY_SIZE) * + PACKET_COUNTER_AVERAGE_INTERVAL)); + + double min_speed_request = 1000.0 * (((double)(total_sent + total_resent)) / ((double)( + CONGESTION_QUEUE_ARRAY_SIZE) * PACKET_COUNTER_AVERAGE_INTERVAL)); + + if (min_speed < CRYPTO_PACKET_MIN_RATE) { + min_speed = CRYPTO_PACKET_MIN_RATE; + } + + double send_array_ratio = (((double)npackets) / min_speed); + + // TODO(irungentoo): Improve formula? + if (send_array_ratio > SEND_QUEUE_RATIO && CRYPTO_MIN_QUEUE_LENGTH < npackets) { + conn->packet_send_rate = min_speed * (1.0 / (send_array_ratio / SEND_QUEUE_RATIO)); + } else if (conn->last_congestion_event + CONGESTION_EVENT_TIMEOUT < temp_time) { + conn->packet_send_rate = min_speed * 1.2; + } else { + conn->packet_send_rate = min_speed * 0.9; + } + + conn->packet_send_rate_requested = min_speed_request * 1.2; + + if (conn->packet_send_rate < CRYPTO_PACKET_MIN_RATE) { + conn->packet_send_rate = CRYPTO_PACKET_MIN_RATE; + } + + if (conn->packet_send_rate_requested < conn->packet_send_rate) { + conn->packet_send_rate_requested = conn->packet_send_rate; + } + } + } + + if (conn->last_packets_left_set == 0 || conn->last_packets_left_requested_set == 0) { + conn->last_packets_left_requested_set = conn->last_packets_left_set = temp_time; + conn->packets_left_requested = conn->packets_left = CRYPTO_MIN_QUEUE_LENGTH; + } else { + if (((uint64_t)((1000.0 / conn->packet_send_rate) + 0.5) + conn->last_packets_left_set) <= temp_time) { + double n_packets = conn->packet_send_rate * (((double)(temp_time - conn->last_packets_left_set)) / 1000.0); + n_packets += conn->last_packets_left_rem; + + uint32_t num_packets = n_packets; + double rem = n_packets - (double)num_packets; + + if (conn->packets_left > num_packets * 4 + CRYPTO_MIN_QUEUE_LENGTH) { + conn->packets_left = num_packets * 4 + CRYPTO_MIN_QUEUE_LENGTH; + } else { + conn->packets_left += num_packets; + } + + conn->last_packets_left_set = temp_time; + conn->last_packets_left_rem = rem; + } + + if (((uint64_t)((1000.0 / conn->packet_send_rate_requested) + 0.5) + conn->last_packets_left_requested_set) <= + temp_time) { + double n_packets = conn->packet_send_rate_requested * (((double)(temp_time - conn->last_packets_left_requested_set)) / + 1000.0); + n_packets += conn->last_packets_left_requested_rem; + + uint32_t num_packets = n_packets; + double rem = n_packets - (double)num_packets; + conn->packets_left_requested = num_packets; + + conn->last_packets_left_requested_set = temp_time; + conn->last_packets_left_requested_rem = rem; + } + + if (conn->packets_left > conn->packets_left_requested) { + conn->packets_left_requested = conn->packets_left; + } + } + + int ret = send_requested_packets(c, i, conn->packets_left_requested); + + if (ret != -1) { + conn->packets_left_requested -= ret; + conn->packets_resent += ret; + + if ((unsigned int)ret < conn->packets_left) { + conn->packets_left -= ret; + } else { + conn->last_congestion_event = temp_time; + conn->packets_left = 0; + } + } + + if (conn->packet_send_rate > CRYPTO_PACKET_MIN_RATE * 1.5) { + total_send_rate += conn->packet_send_rate; + } + } + } + + c->current_sleep_time = ~0; + uint32_t sleep_time = peak_request_packet_interval; + + if (c->current_sleep_time > sleep_time) { + c->current_sleep_time = sleep_time; + } + + if (total_send_rate > CRYPTO_PACKET_MIN_RATE) { + sleep_time = (1000.0 / total_send_rate); + + if (c->current_sleep_time > sleep_time) { + c->current_sleep_time = sleep_time + 1; + } + } + + sleep_time = CRYPTO_SEND_PACKET_INTERVAL; + + if (c->current_sleep_time > sleep_time) { + c->current_sleep_time = sleep_time; + } +} + +/* Return 1 if max speed was reached for this connection (no more data can be physically through the pipe). + * Return 0 if it wasn't reached. + */ +bool max_speed_reached(Net_Crypto *c, int crypt_connection_id) +{ + return reset_max_speed_reached(c, crypt_connection_id) != 0; +} + +/* returns the number of packet slots left in the sendbuffer. + * return 0 if failure. + */ +uint32_t crypto_num_free_sendqueue_slots(const Net_Crypto *c, int crypt_connection_id) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) { + return 0; + } + + uint32_t max_packets = CRYPTO_PACKET_BUFFER_SIZE - num_packets_array(&conn->send_array); + + if (conn->packets_left < max_packets) { + return conn->packets_left; + } + + return max_packets; +} + +/* Sends a lossless cryptopacket. + * + * return -1 if data could not be put in packet queue. + * return positive packet number if data was put into the queue. + * + * congestion_control: should congestion control apply to this packet? + */ +int64_t write_cryptpacket(Net_Crypto *c, int crypt_connection_id, const uint8_t *data, uint16_t length, + uint8_t congestion_control) +{ + if (length == 0) { + return -1; + } + + if (data[0] < CRYPTO_RESERVED_PACKETS) { + return -1; + } + + if (data[0] >= PACKET_ID_LOSSY_RANGE_START) { + return -1; + } + + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) { + return -1; + } + + if (conn->status != CRYPTO_CONN_ESTABLISHED) { + return -1; + } + + if (congestion_control && conn->packets_left == 0) { + return -1; + } + + int64_t ret = send_lossless_packet(c, crypt_connection_id, data, length, congestion_control); + + if (ret == -1) { + return -1; + } + + if (congestion_control) { + --conn->packets_left; + --conn->packets_left_requested; + conn->packets_sent++; + } + + return ret; +} + +/* Check if packet_number was received by the other side. + * + * packet_number must be a valid packet number of a packet sent on this connection. + * + * return -1 on failure. + * return 0 on success. + */ +int cryptpacket_received(Net_Crypto *c, int crypt_connection_id, uint32_t packet_number) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) { + return -1; + } + + uint32_t num = conn->send_array.buffer_end - conn->send_array.buffer_start; + uint32_t num1 = packet_number - conn->send_array.buffer_start; + + if (num < num1) { + return 0; + } + + return -1; +} + +/* return -1 on failure. + * return 0 on success. + * + * Sends a lossy cryptopacket. (first byte must in the PACKET_ID_LOSSY_RANGE_*) + */ +int send_lossy_cryptpacket(Net_Crypto *c, int crypt_connection_id, const uint8_t *data, uint16_t length) +{ + if (length == 0 || length > MAX_CRYPTO_DATA_SIZE) { + return -1; + } + + if (data[0] < PACKET_ID_LOSSY_RANGE_START) { + return -1; + } + + if (data[0] >= (PACKET_ID_LOSSY_RANGE_START + PACKET_ID_LOSSY_RANGE_SIZE)) { + return -1; + } + + pthread_mutex_lock(&c->connections_mutex); + ++c->connection_use_counter; + pthread_mutex_unlock(&c->connections_mutex); + + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + int ret = -1; + + if (conn) { + pthread_mutex_lock(&conn->mutex); + uint32_t buffer_start = conn->recv_array.buffer_start; + uint32_t buffer_end = conn->send_array.buffer_end; + pthread_mutex_unlock(&conn->mutex); + ret = send_data_packet_helper(c, crypt_connection_id, buffer_start, buffer_end, data, length); + } + + pthread_mutex_lock(&c->connections_mutex); + --c->connection_use_counter; + pthread_mutex_unlock(&c->connections_mutex); + + return ret; +} + +/* Kill a crypto connection. + * + * return -1 on failure. + * return 0 on success. + */ +int crypto_kill(Net_Crypto *c, int crypt_connection_id) +{ + while (1) { /* TODO(irungentoo): is this really the best way to do this? */ + pthread_mutex_lock(&c->connections_mutex); + + if (!c->connection_use_counter) { + break; + } + + pthread_mutex_unlock(&c->connections_mutex); + } + + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + int ret = -1; + + if (conn) { + if (conn->status == CRYPTO_CONN_ESTABLISHED) { + send_kill_packet(c, crypt_connection_id); + } + + pthread_mutex_lock(&c->tcp_mutex); + kill_tcp_connection_to(c->tcp_c, conn->connection_number_tcp); + pthread_mutex_unlock(&c->tcp_mutex); + + bs_list_remove(&c->ip_port_list, (uint8_t *)&conn->ip_portv4, crypt_connection_id); + bs_list_remove(&c->ip_port_list, (uint8_t *)&conn->ip_portv6, crypt_connection_id); + clear_temp_packet(c, crypt_connection_id); + clear_buffer(&conn->send_array); + clear_buffer(&conn->recv_array); + ret = wipe_crypto_connection(c, crypt_connection_id); + } + + pthread_mutex_unlock(&c->connections_mutex); + + return ret; +} + +/* return one of CRYPTO_CONN_* values indicating the state of the connection. + * + * sets direct_connected to 1 if connection connects directly to other, 0 if it isn't. + * sets online_tcp_relays to the number of connected tcp relays this connection has. + */ +unsigned int crypto_connection_status(const Net_Crypto *c, int crypt_connection_id, bool *direct_connected, + unsigned int *online_tcp_relays) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) { + return CRYPTO_CONN_NO_CONNECTION; + } + + if (direct_connected) { + *direct_connected = 0; + + uint64_t current_time = unix_time(); + + if ((UDP_DIRECT_TIMEOUT + conn->direct_lastrecv_timev4) > current_time) { + *direct_connected = 1; + } + + if ((UDP_DIRECT_TIMEOUT + conn->direct_lastrecv_timev6) > current_time) { + *direct_connected = 1; + } + } + + if (online_tcp_relays) { + *online_tcp_relays = tcp_connection_to_online_tcp_relays(c->tcp_c, conn->connection_number_tcp); + } + + return conn->status; +} + +void new_keys(Net_Crypto *c) +{ + crypto_new_keypair(c->self_public_key, c->self_secret_key); +} + +/* Save the public and private keys to the keys array. + * Length must be CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SECRET_KEY_SIZE. + * + * TODO(irungentoo): Save only secret key. + */ +void save_keys(const Net_Crypto *c, uint8_t *keys) +{ + memcpy(keys, c->self_public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(keys + CRYPTO_PUBLIC_KEY_SIZE, c->self_secret_key, CRYPTO_SECRET_KEY_SIZE); +} + +/* Load the secret key. + * Length must be CRYPTO_SECRET_KEY_SIZE. + */ +void load_secret_key(Net_Crypto *c, const uint8_t *sk) +{ + memcpy(c->self_secret_key, sk, CRYPTO_SECRET_KEY_SIZE); + crypto_derive_public_key(c->self_public_key, c->self_secret_key); +} + +/* Run this to (re)initialize net_crypto. + * Sets all the global connection variables to their default values. + */ +Net_Crypto *new_net_crypto(Logger *log, DHT *dht, TCP_Proxy_Info *proxy_info) +{ + unix_time_update(); + + if (dht == NULL) { + return NULL; + } + + Net_Crypto *temp = (Net_Crypto *)calloc(1, sizeof(Net_Crypto)); + + if (temp == NULL) { + return NULL; + } + + temp->log = log; + + temp->tcp_c = new_tcp_connections(dht->self_secret_key, proxy_info); + + if (temp->tcp_c == NULL) { + free(temp); + return NULL; + } + + set_packet_tcp_connection_callback(temp->tcp_c, &tcp_data_callback, temp); + set_oob_packet_tcp_connection_callback(temp->tcp_c, &tcp_oob_callback, temp); + + /*if (create_recursive_mutex(&temp->tcp_mutex) != 0 || + pthread_mutex_init(&temp->connections_mutex, NULL) != 0) { + kill_tcp_connections(temp->tcp_c); + free(temp); + return NULL; + }*/ + + temp->dht = dht; + + new_keys(temp); + new_symmetric_key(temp->secret_symmetric_key); + + temp->current_sleep_time = CRYPTO_SEND_PACKET_INTERVAL; + + networking_registerhandler(dht->net, NET_PACKET_COOKIE_REQUEST, &udp_handle_cookie_request, temp); + networking_registerhandler(dht->net, NET_PACKET_COOKIE_RESPONSE, &udp_handle_packet, temp); + networking_registerhandler(dht->net, NET_PACKET_CRYPTO_HS, &udp_handle_packet, temp); + networking_registerhandler(dht->net, NET_PACKET_CRYPTO_DATA, &udp_handle_packet, temp); + + bs_list_init(&temp->ip_port_list, sizeof(IP_Port), 8); + + return temp; +} + +static void kill_timedout(Net_Crypto *c, void *userdata) +{ + uint32_t i; + //uint64_t temp_time = current_time_monotonic(); + + for (i = 0; i < c->crypto_connections_length; ++i) { + Crypto_Connection *conn = get_crypto_connection(c, i); + + if (conn == 0) { + return; + } + + if (conn->status == CRYPTO_CONN_NO_CONNECTION) { + continue; + } + + if (conn->status == CRYPTO_CONN_COOKIE_REQUESTING || conn->status == CRYPTO_CONN_HANDSHAKE_SENT + || conn->status == CRYPTO_CONN_NOT_CONFIRMED) { + if (conn->temp_packet_num_sent < MAX_NUM_SENDPACKET_TRIES) { + continue; + } + + connection_kill(c, i, userdata); + } + +#if 0 + + if (conn->status == CRYPTO_CONN_ESTABLISHED) { + // TODO(irungentoo): add a timeout here? + } + +#endif + } +} + +/* return the optimal interval in ms for running do_net_crypto. + */ +uint32_t crypto_run_interval(const Net_Crypto *c) +{ + return c->current_sleep_time; +} + +/* Main loop. */ +void do_net_crypto(Net_Crypto *c, void *userdata) +{ + unix_time_update(); + kill_timedout(c, userdata); + do_tcp(c, userdata); + send_crypto_packets(c); +} + +void kill_net_crypto(Net_Crypto *c) +{ + uint32_t i; + + for (i = 0; i < c->crypto_connections_length; ++i) { + crypto_kill(c, i); + } + + pthread_mutex_destroy(&c->tcp_mutex); + pthread_mutex_destroy(&c->connections_mutex); + + kill_tcp_connections(c->tcp_c); + bs_list_free(&c->ip_port_list); + networking_registerhandler(c->dht->net, NET_PACKET_COOKIE_REQUEST, NULL, NULL); + networking_registerhandler(c->dht->net, NET_PACKET_COOKIE_RESPONSE, NULL, NULL); + networking_registerhandler(c->dht->net, NET_PACKET_CRYPTO_HS, NULL, NULL); + networking_registerhandler(c->dht->net, NET_PACKET_CRYPTO_DATA, NULL, NULL); + crypto_memzero(c, sizeof(Net_Crypto)); + free(c); +} diff --git a/libs/libtox/src/toxcore/net_crypto.h b/libs/libtox/src/toxcore/net_crypto.h new file mode 100644 index 0000000000..5ec5ca94fb --- /dev/null +++ b/libs/libtox/src/toxcore/net_crypto.h @@ -0,0 +1,428 @@ +/* + * Functions for the core network crypto. + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2013 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifndef NET_CRYPTO_H +#define NET_CRYPTO_H + +#include "DHT.h" +#include "LAN_discovery.h" +#include "TCP_connection.h" +#include "logger.h" + +#include <pthread.h> + +#define CRYPTO_CONN_NO_CONNECTION 0 +#define CRYPTO_CONN_COOKIE_REQUESTING 1 //send cookie request packets +#define CRYPTO_CONN_HANDSHAKE_SENT 2 //send handshake packets +#define CRYPTO_CONN_NOT_CONFIRMED 3 //send handshake packets, we have received one from the other +#define CRYPTO_CONN_ESTABLISHED 4 + +/* Maximum size of receiving and sending packet buffers. */ +#define CRYPTO_PACKET_BUFFER_SIZE 32768 /* Must be a power of 2 */ + +/* Minimum packet rate per second. */ +#define CRYPTO_PACKET_MIN_RATE 4.0 + +/* Minimum packet queue max length. */ +#define CRYPTO_MIN_QUEUE_LENGTH 64 + +/* Maximum total size of packets that net_crypto sends. */ +#define MAX_CRYPTO_PACKET_SIZE 1400 + +#define CRYPTO_DATA_PACKET_MIN_SIZE (1 + sizeof(uint16_t) + (sizeof(uint32_t) + sizeof(uint32_t)) + CRYPTO_MAC_SIZE) + +/* Max size of data in packets */ +#define MAX_CRYPTO_DATA_SIZE (MAX_CRYPTO_PACKET_SIZE - CRYPTO_DATA_PACKET_MIN_SIZE) + +/* Interval in ms between sending cookie request/handshake packets. */ +#define CRYPTO_SEND_PACKET_INTERVAL 1000 + +/* The maximum number of times we try to send the cookie request and handshake + before giving up. */ +#define MAX_NUM_SENDPACKET_TRIES 8 + +/* The timeout of no received UDP packets before the direct UDP connection is considered dead. */ +#define UDP_DIRECT_TIMEOUT ((MAX_NUM_SENDPACKET_TRIES * CRYPTO_SEND_PACKET_INTERVAL) / 1000) + +#define PACKET_ID_PADDING 0 /* Denotes padding */ +#define PACKET_ID_REQUEST 1 /* Used to request unreceived packets */ +#define PACKET_ID_KILL 2 /* Used to kill connection */ + +/* Packet ids 0 to CRYPTO_RESERVED_PACKETS - 1 are reserved for use by net_crypto. */ +#define CRYPTO_RESERVED_PACKETS 16 + +#define MAX_TCP_CONNECTIONS 64 +#define MAX_TCP_RELAYS_PEER 4 + +/* All packets starting with a byte in this range are considered lossy packets. */ +#define PACKET_ID_LOSSY_RANGE_START 192 +#define PACKET_ID_LOSSY_RANGE_SIZE 63 + +#define CRYPTO_MAX_PADDING 8 /* All packets will be padded a number of bytes based on this number. */ + +/* Base current transfer speed on last CONGESTION_QUEUE_ARRAY_SIZE number of points taken + at the dT defined in net_crypto.c */ +#define CONGESTION_QUEUE_ARRAY_SIZE 12 +#define CONGESTION_LAST_SENT_ARRAY_SIZE (CONGESTION_QUEUE_ARRAY_SIZE * 2) + +/* Default connection ping in ms. */ +#define DEFAULT_PING_CONNECTION 1000 +#define DEFAULT_TCP_PING_CONNECTION 500 + +typedef struct { + uint64_t sent_time; + uint16_t length; + uint8_t data[MAX_CRYPTO_DATA_SIZE]; +} Packet_Data; + +typedef struct { + Packet_Data *buffer[CRYPTO_PACKET_BUFFER_SIZE]; + uint32_t buffer_start; + uint32_t buffer_end; /* packet numbers in array: {buffer_start, buffer_end) */ +} Packets_Array; + +typedef struct { + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The real public key of the peer. */ + uint8_t recv_nonce[CRYPTO_NONCE_SIZE]; /* Nonce of received packets. */ + uint8_t sent_nonce[CRYPTO_NONCE_SIZE]; /* Nonce of sent packets. */ + uint8_t sessionpublic_key[CRYPTO_PUBLIC_KEY_SIZE]; /* Our public key for this session. */ + uint8_t sessionsecret_key[CRYPTO_SECRET_KEY_SIZE]; /* Our private key for this session. */ + uint8_t peersessionpublic_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The public key of the peer. */ + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; /* The precomputed shared key from encrypt_precompute. */ + uint8_t status; /* 0 if no connection, 1 we are sending cookie request packets, + * 2 if we are sending handshake packets + * 3 if connection is not confirmed yet (we have received a handshake but no data packets yet), + * 4 if the connection is established. + */ + uint64_t cookie_request_number; /* number used in the cookie request packets for this connection */ + uint8_t dht_public_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The dht public key of the peer */ + + uint8_t *temp_packet; /* Where the cookie request/handshake packet is stored while it is being sent. */ + uint16_t temp_packet_length; + uint64_t temp_packet_sent_time; /* The time at which the last temp_packet was sent in ms. */ + uint32_t temp_packet_num_sent; + + IP_Port ip_portv4; /* The ip and port to contact this guy directly.*/ + IP_Port ip_portv6; + uint64_t direct_lastrecv_timev4; /* The Time at which we last received a direct packet in ms. */ + uint64_t direct_lastrecv_timev6; + + uint64_t last_tcp_sent; /* Time the last TCP packet was sent. */ + + Packets_Array send_array; + Packets_Array recv_array; + + int (*connection_status_callback)(void *object, int id, uint8_t status, void *userdata); + void *connection_status_callback_object; + int connection_status_callback_id; + + int (*connection_data_callback)(void *object, int id, const uint8_t *data, uint16_t length, void *userdata); + void *connection_data_callback_object; + int connection_data_callback_id; + + int (*connection_lossy_data_callback)(void *object, int id, const uint8_t *data, uint16_t length, void *userdata); + void *connection_lossy_data_callback_object; + int connection_lossy_data_callback_id; + + uint64_t last_request_packet_sent; + uint64_t direct_send_attempt_time; + + uint32_t packet_counter; + double packet_recv_rate; + uint64_t packet_counter_set; + + double packet_send_rate; + uint32_t packets_left; + uint64_t last_packets_left_set; + double last_packets_left_rem; + + double packet_send_rate_requested; + uint32_t packets_left_requested; + uint64_t last_packets_left_requested_set; + double last_packets_left_requested_rem; + + uint32_t last_sendqueue_size[CONGESTION_QUEUE_ARRAY_SIZE], last_sendqueue_counter; + long signed int last_num_packets_sent[CONGESTION_LAST_SENT_ARRAY_SIZE], + last_num_packets_resent[CONGESTION_LAST_SENT_ARRAY_SIZE]; + uint32_t packets_sent, packets_resent; + uint64_t last_congestion_event; + uint64_t rtt_time; + + /* TCP_connection connection_number */ + unsigned int connection_number_tcp; + + uint8_t maximum_speed_reached; + + pthread_mutex_t mutex; + + void (*dht_pk_callback)(void *data, int32_t number, const uint8_t *dht_public_key, void *userdata); + void *dht_pk_callback_object; + uint32_t dht_pk_callback_number; +} Crypto_Connection; + +typedef struct { + IP_Port source; + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The real public key of the peer. */ + uint8_t dht_public_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The dht public key of the peer. */ + uint8_t recv_nonce[CRYPTO_NONCE_SIZE]; /* Nonce of received packets. */ + uint8_t peersessionpublic_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The public key of the peer. */ + uint8_t *cookie; + uint8_t cookie_length; +} New_Connection; + +typedef struct { + Logger *log; + + DHT *dht; + TCP_Connections *tcp_c; + + Crypto_Connection *crypto_connections; + pthread_mutex_t tcp_mutex; + + pthread_mutex_t connections_mutex; + unsigned int connection_use_counter; + + uint32_t crypto_connections_length; /* Length of connections array. */ + + /* Our public and secret keys. */ + uint8_t self_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t self_secret_key[CRYPTO_SECRET_KEY_SIZE]; + + /* The secret key used for cookies */ + uint8_t secret_symmetric_key[CRYPTO_SYMMETRIC_KEY_SIZE]; + + int (*new_connection_callback)(void *object, New_Connection *n_c); + void *new_connection_callback_object; + + /* The current optimal sleep time */ + uint32_t current_sleep_time; + + BS_LIST ip_port_list; +} Net_Crypto; + + +/* Set function to be called when someone requests a new connection to us. + * + * The set function should return -1 on failure and 0 on success. + * + * n_c is only valid for the duration of the function call. + */ +void new_connection_handler(Net_Crypto *c, int (*new_connection_callback)(void *object, New_Connection *n_c), + void *object); + +/* Accept a crypto connection. + * + * return -1 on failure. + * return connection id on success. + */ +int accept_crypto_connection(Net_Crypto *c, New_Connection *n_c); + +/* Create a crypto connection. + * If one to that real public key already exists, return it. + * + * return -1 on failure. + * return connection id on success. + */ +int new_crypto_connection(Net_Crypto *c, const uint8_t *real_public_key, const uint8_t *dht_public_key); + +/* Set the direct ip of the crypto connection. + * + * Connected is 0 if we are not sure we are connected to that person, 1 if we are sure. + * + * return -1 on failure. + * return 0 on success. + */ +int set_direct_ip_port(Net_Crypto *c, int crypt_connection_id, IP_Port ip_port, bool connected); + +/* Set function to be called when connection with crypt_connection_id goes connects/disconnects. + * + * The set function should return -1 on failure and 0 on success. + * Note that if this function is set, the connection will clear itself on disconnect. + * Object and id will be passed to this function untouched. + * status is 1 if the connection is going online, 0 if it is going offline. + * + * return -1 on failure. + * return 0 on success. + */ +int connection_status_handler(const Net_Crypto *c, int crypt_connection_id, + int (*connection_status_callback)(void *object, int id, uint8_t status, void *userdata), void *object, int id); + +/* Set function to be called when connection with crypt_connection_id receives a lossless data packet of length. + * + * The set function should return -1 on failure and 0 on success. + * Object and id will be passed to this function untouched. + * + * return -1 on failure. + * return 0 on success. + */ +int connection_data_handler(const Net_Crypto *c, int crypt_connection_id, int (*connection_data_callback)(void *object, + int id, const uint8_t *data, uint16_t length, void *userdata), void *object, int id); + + +/* Set function to be called when connection with crypt_connection_id receives a lossy data packet of length. + * + * The set function should return -1 on failure and 0 on success. + * Object and id will be passed to this function untouched. + * + * return -1 on failure. + * return 0 on success. + */ +int connection_lossy_data_handler(Net_Crypto *c, int crypt_connection_id, + int (*connection_lossy_data_callback)(void *object, int id, const uint8_t *data, uint16_t length, void *userdata), + void *object, + int id); + +/* Set the function for this friend that will be callbacked with object and number if + * the friend sends us a different dht public key than we have associated to him. + * + * If this function is called, the connection should be recreated with the new public key. + * + * object and number will be passed as argument to this function. + * + * return -1 on failure. + * return 0 on success. + */ +int nc_dht_pk_callback(Net_Crypto *c, int crypt_connection_id, void (*function)(void *data, int32_t number, + const uint8_t *dht_public_key, void *userdata), void *object, uint32_t number); + +/* returns the number of packet slots left in the sendbuffer. + * return 0 if failure. + */ +uint32_t crypto_num_free_sendqueue_slots(const Net_Crypto *c, int crypt_connection_id); + +/* Return 1 if max speed was reached for this connection (no more data can be physically through the pipe). + * Return 0 if it wasn't reached. + */ +bool max_speed_reached(Net_Crypto *c, int crypt_connection_id); + +/* Sends a lossless cryptopacket. + * + * return -1 if data could not be put in packet queue. + * return positive packet number if data was put into the queue. + * + * The first byte of data must be in the CRYPTO_RESERVED_PACKETS to PACKET_ID_LOSSY_RANGE_START range. + * + * congestion_control: should congestion control apply to this packet? + */ +int64_t write_cryptpacket(Net_Crypto *c, int crypt_connection_id, const uint8_t *data, uint16_t length, + uint8_t congestion_control); + +/* Check if packet_number was received by the other side. + * + * packet_number must be a valid packet number of a packet sent on this connection. + * + * return -1 on failure. + * return 0 on success. + */ +int cryptpacket_received(Net_Crypto *c, int crypt_connection_id, uint32_t packet_number); + +/* return -1 on failure. + * return 0 on success. + * + * Sends a lossy cryptopacket. (first byte must in the PACKET_ID_LOSSY_RANGE_*) + */ +int send_lossy_cryptpacket(Net_Crypto *c, int crypt_connection_id, const uint8_t *data, uint16_t length); + +/* Add a tcp relay, associating it to a crypt_connection_id. + * + * return 0 if it was added. + * return -1 if it wasn't. + */ +int add_tcp_relay_peer(Net_Crypto *c, int crypt_connection_id, IP_Port ip_port, const uint8_t *public_key); + +/* Add a tcp relay to the array. + * + * return 0 if it was added. + * return -1 if it wasn't. + */ +int add_tcp_relay(Net_Crypto *c, IP_Port ip_port, const uint8_t *public_key); + +/* Return a random TCP connection number for use in send_tcp_onion_request. + * + * return TCP connection number on success. + * return -1 on failure. + */ +int get_random_tcp_con_number(Net_Crypto *c); + +/* Send an onion packet via the TCP relay corresponding to TCP_conn_number. + * + * return 0 on success. + * return -1 on failure. + */ +int send_tcp_onion_request(Net_Crypto *c, unsigned int tcp_connections_number, const uint8_t *data, uint16_t length); + +/* Copy a maximum of num TCP relays we are connected to to tcp_relays. + * NOTE that the family of the copied ip ports will be set to TCP_INET or TCP_INET6. + * + * return number of relays copied to tcp_relays on success. + * return 0 on failure. + */ +unsigned int copy_connected_tcp_relays(Net_Crypto *c, Node_format *tcp_relays, uint16_t num); + +/* Kill a crypto connection. + * + * return -1 on failure. + * return 0 on success. + */ +int crypto_kill(Net_Crypto *c, int crypt_connection_id); + +/* return one of CRYPTO_CONN_* values indicating the state of the connection. + * + * sets direct_connected to 1 if connection connects directly to other, 0 if it isn't. + * sets online_tcp_relays to the number of connected tcp relays this connection has. + */ +unsigned int crypto_connection_status(const Net_Crypto *c, int crypt_connection_id, bool *direct_connected, + unsigned int *online_tcp_relays); + +/* Generate our public and private keys. + * Only call this function the first time the program starts. + */ +void new_keys(Net_Crypto *c); + +/* Save the public and private keys to the keys array. + * Length must be CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SECRET_KEY_SIZE. + */ +void save_keys(const Net_Crypto *c, uint8_t *keys); + +/* Load the secret key. + * Length must be CRYPTO_SECRET_KEY_SIZE. + */ +void load_secret_key(Net_Crypto *c, const uint8_t *sk); + +/* Create new instance of Net_Crypto. + * Sets all the global connection variables to their default values. + */ +Net_Crypto *new_net_crypto(Logger *log, DHT *dht, TCP_Proxy_Info *proxy_info); + +/* return the optimal interval in ms for running do_net_crypto. + */ +uint32_t crypto_run_interval(const Net_Crypto *c); + +/* Main loop. */ +void do_net_crypto(Net_Crypto *c, void *userdata); + +void kill_net_crypto(Net_Crypto *c); + + + +#endif diff --git a/libs/libtox/src/toxcore/network.c b/libs/libtox/src/toxcore/network.c new file mode 100644 index 0000000000..5c43bf5779 --- /dev/null +++ b/libs/libtox/src/toxcore/network.c @@ -0,0 +1,1446 @@ +/* + * Functions for the core networking. + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2013 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define _DARWIN_C_SOURCE +#define _XOPEN_SOURCE 600 + +#if defined(_WIN32) && _WIN32_WINNT >= _WIN32_WINNT_WINXP +#define _WIN32_WINNT 0x501 +#endif + +#include "network.h" + +#include "logger.h" +#include "util.h" + +#include <assert.h> +#ifdef __APPLE__ +#include <mach/clock.h> +#include <mach/mach.h> +#endif + +#ifndef IPV6_ADD_MEMBERSHIP +#ifdef IPV6_JOIN_GROUP +#define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP +#define IPV6_DROP_MEMBERSHIP IPV6_LEAVE_GROUP +#endif +#endif + +#if !(defined(_WIN32) || defined(__WIN32__) || defined(WIN32)) + +#include <errno.h> +#include <fcntl.h> +#include <netdb.h> +#include <sys/time.h> +#include <sys/types.h> + +#else + +#ifndef IPV6_V6ONLY +#define IPV6_V6ONLY 27 +#endif + +#ifndef EWOULDBLOCK +#define EWOULDBLOCK WSAEWOULDBLOCK +#endif + +static const char *inet_ntop(Family family, const void *addr, char *buf, size_t bufsize) +{ + if (family == TOX_AF_INET) { + struct sockaddr_in saddr; + memset(&saddr, 0, sizeof(saddr)); + + saddr.sin_family = AF_INET; + saddr.sin_addr = *(const struct in_addr *)addr; + + DWORD len = bufsize; + + if (WSAAddressToString((LPSOCKADDR)&saddr, sizeof(saddr), NULL, buf, &len)) { + return NULL; + } + + return buf; + } else if (family == TOX_AF_INET6) { + struct sockaddr_in6 saddr; + memset(&saddr, 0, sizeof(saddr)); + + saddr.sin6_family = AF_INET6; + saddr.sin6_addr = *(const struct in6_addr *)addr; + + DWORD len = bufsize; + + if (WSAAddressToString((LPSOCKADDR)&saddr, sizeof(saddr), NULL, buf, &len)) { + return NULL; + } + + return buf; + } + + return NULL; +} + +static int inet_pton(Family family, const char *addrString, void *addrbuf) +{ + if (family == TOX_AF_INET) { + struct sockaddr_in saddr; + memset(&saddr, 0, sizeof(saddr)); + + INT len = sizeof(saddr); + + if (WSAStringToAddress((LPTSTR)addrString, AF_INET, NULL, (LPSOCKADDR)&saddr, &len)) { + return 0; + } + + *(struct in_addr *)addrbuf = saddr.sin_addr; + + return 1; + } else if (family == TOX_AF_INET6) { + struct sockaddr_in6 saddr; + memset(&saddr, 0, sizeof(saddr)); + + INT len = sizeof(saddr); + + if (WSAStringToAddress((LPTSTR)addrString, AF_INET6, NULL, (LPSOCKADDR)&saddr, &len)) { + return 0; + } + + *(struct in6_addr *)addrbuf = saddr.sin6_addr; + + return 1; + } + + return 0; +} + +#endif + +#if TOX_INET6_ADDRSTRLEN < INET6_ADDRSTRLEN +#error TOX_INET6_ADDRSTRLEN should be greater or equal to INET6_ADDRSTRLEN (#INET6_ADDRSTRLEN) +#endif + +#if TOX_INET_ADDRSTRLEN < INET_ADDRSTRLEN +#error TOX_INET_ADDRSTRLEN should be greater or equal to INET_ADDRSTRLEN (#INET_ADDRSTRLEN) +#endif + +static int make_proto(int proto); +static int make_socktype(int type); +static int make_family(int tox_family); +static int make_tox_family(int family); + +static void get_ip4(IP4 *result, const struct in_addr *addr) +{ + result->uint32 = addr->s_addr; +} + +static void get_ip6(IP6 *result, const struct in6_addr *addr) +{ + assert(sizeof(result->uint8) == sizeof(addr->s6_addr)); + memcpy(result->uint8, addr->s6_addr, sizeof(result->uint8)); +} + +static void fill_addr4(IP4 ip, struct in_addr *addr) +{ + addr->s_addr = ip.uint32; +} + +static void fill_addr6(IP6 ip, struct in6_addr *addr) +{ + assert(sizeof(ip.uint8) == sizeof(addr->s6_addr)); + memcpy(addr->s6_addr, ip.uint8, sizeof(ip.uint8)); +} + +#if !defined(INADDR_LOOPBACK) +#define INADDR_LOOPBACK 0x7f000001 +#endif + +const IP4 IP4_BROADCAST = { INADDR_BROADCAST }; +const IP6 IP6_BROADCAST = { + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } +}; + +IP4 get_ip4_loopback() +{ + IP4 loopback; + loopback.uint32 = htonl(INADDR_LOOPBACK); + return loopback; +} + +IP6 get_ip6_loopback() +{ + IP6 loopback; + get_ip6(&loopback, &in6addr_loopback); + return loopback; +} + +/* Check if socket is valid. + * + * return 1 if valid + * return 0 if not valid + */ +int sock_valid(Socket sock) +{ +#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) + + if (sock == INVALID_SOCKET) { +#else + + if (sock < 0) { +#endif + return 0; + } + + return 1; +} + +/* Close the socket. + */ +void kill_sock(Socket sock) +{ +#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) + closesocket(sock); +#else + close(sock); +#endif +} + +/* Set socket as nonblocking + * + * return 1 on success + * return 0 on failure + */ +int set_socket_nonblock(Socket sock) +{ +#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) + u_long mode = 1; + return (ioctlsocket(sock, FIONBIO, &mode) == 0); +#else + return (fcntl(sock, F_SETFL, O_NONBLOCK, 1) == 0); +#endif +} + +/* Set socket to not emit SIGPIPE + * + * return 1 on success + * return 0 on failure + */ +int set_socket_nosigpipe(Socket sock) +{ +#if defined(__MACH__) + int set = 1; + return (setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, (const char *)&set, sizeof(int)) == 0); +#else + return 1; +#endif +} + +/* Enable SO_REUSEADDR on socket. + * + * return 1 on success + * return 0 on failure + */ +int set_socket_reuseaddr(Socket sock) +{ + int set = 1; + return (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&set, sizeof(set)) == 0); +} + +/* Set socket to dual (IPv4 + IPv6 socket) + * + * return 1 on success + * return 0 on failure + */ +int set_socket_dualstack(Socket sock) +{ + int ipv6only = 0; + socklen_t optsize = sizeof(ipv6only); + int res = getsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&ipv6only, &optsize); + + if ((res == 0) && (ipv6only == 0)) { + return 1; + } + + ipv6only = 0; + return (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (const char *)&ipv6only, sizeof(ipv6only)) == 0); +} + + +/* return current UNIX time in microseconds (us). */ +static uint64_t current_time_actual(void) +{ + uint64_t time; +#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) + /* This probably works fine */ + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + time = ft.dwHighDateTime; + time <<= 32; + time |= ft.dwLowDateTime; + time -= 116444736000000000ULL; + return time / 10; +#else + struct timeval a; + gettimeofday(&a, NULL); + time = 1000000ULL * a.tv_sec + a.tv_usec; + return time; +#endif +} + + +#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) +static uint64_t last_monotime; +static uint64_t add_monotime; +#endif + +/* return current monotonic time in milliseconds (ms). */ +uint64_t current_time_monotonic(void) +{ + uint64_t time; +#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) + uint64_t old_add_monotime = add_monotime; + time = (uint64_t)GetTickCount() + add_monotime; + + /* Check if time has decreased because of 32 bit wrap from GetTickCount(), while avoiding false positives from race + * conditions when multiple threads call this function at once */ + if (time + 0x10000 < last_monotime) { + uint32_t add = ~0; + /* use old_add_monotime rather than simply incrementing add_monotime, to handle the case that many threads + * simultaneously detect an overflow */ + add_monotime = old_add_monotime + add; + time += add; + } + + last_monotime = time; +#else + struct timespec monotime; +#if defined(__linux__) && defined(CLOCK_MONOTONIC_RAW) + clock_gettime(CLOCK_MONOTONIC_RAW, &monotime); +#elif defined(__APPLE__) + clock_serv_t muhclock; + mach_timespec_t machtime; + + host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &muhclock); + clock_get_time(muhclock, &machtime); + mach_port_deallocate(mach_task_self(), muhclock); + + monotime.tv_sec = machtime.tv_sec; + monotime.tv_nsec = machtime.tv_nsec; +#else + clock_gettime(CLOCK_MONOTONIC, &monotime); +#endif + time = 1000ULL * monotime.tv_sec + (monotime.tv_nsec / 1000000ULL); +#endif + return time; +} + +static uint32_t data_0(uint16_t buflen, const uint8_t *buffer) +{ + return buflen > 4 ? net_ntohl(*(const uint32_t *)&buffer[1]) : 0; +} +static uint32_t data_1(uint16_t buflen, const uint8_t *buffer) +{ + return buflen > 7 ? net_ntohl(*(const uint32_t *)&buffer[5]) : 0; +} + +static void loglogdata(Logger *log, const char *message, const uint8_t *buffer, + uint16_t buflen, IP_Port ip_port, int res) +{ + char ip_str[IP_NTOA_LEN]; + + if (res < 0) { /* Windows doesn't necessarily know %zu */ + LOGGER_TRACE(log, "[%2u] %s %3hu%c %s:%hu (%u: %s) | %04x%04x", + buffer[0], message, (buflen < 999 ? (uint16_t)buflen : 999), 'E', + ip_ntoa(&ip_port.ip, ip_str, sizeof(ip_str)), net_ntohs(ip_port.port), errno, + strerror(errno), data_0(buflen, buffer), data_1(buflen, buffer)); + } else if ((res > 0) && ((size_t)res <= buflen)) { + LOGGER_TRACE(log, "[%2u] %s %3zu%c %s:%hu (%u: %s) | %04x%04x", + buffer[0], message, (res < 999 ? (size_t)res : 999), ((size_t)res < buflen ? '<' : '='), + ip_ntoa(&ip_port.ip, ip_str, sizeof(ip_str)), net_ntohs(ip_port.port), 0, "OK", + data_0(buflen, buffer), data_1(buflen, buffer)); + } else { /* empty or overwrite */ + LOGGER_TRACE(log, "[%2u] %s %zu%c%zu %s:%hu (%u: %s) | %04x%04x", + buffer[0], message, (size_t)res, (!res ? '!' : '>'), buflen, + ip_ntoa(&ip_port.ip, ip_str, sizeof(ip_str)), net_ntohs(ip_port.port), 0, "OK", + data_0(buflen, buffer), data_1(buflen, buffer)); + } +} + +/* Basic network functions: + * Function to send packet(data) of length length to ip_port. + */ +int sendpacket(Networking_Core *net, IP_Port ip_port, const uint8_t *data, uint16_t length) +{ + if (net->family == 0) { /* Socket not initialized */ + return -1; + } + + /* socket TOX_AF_INET, but target IP NOT: can't send */ + if ((net->family == TOX_AF_INET) && (ip_port.ip.family != TOX_AF_INET)) { + return -1; + } + + struct sockaddr_storage addr; + + size_t addrsize = 0; + + if (ip_port.ip.family == TOX_AF_INET) { + if (net->family == TOX_AF_INET6) { + /* must convert to IPV4-in-IPV6 address */ + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr; + + addrsize = sizeof(struct sockaddr_in6); + addr6->sin6_family = AF_INET6; + addr6->sin6_port = ip_port.port; + + /* there should be a macro for this in a standards compliant + * environment, not found */ + IP6 ip6; + + ip6.uint32[0] = 0; + ip6.uint32[1] = 0; + ip6.uint32[2] = net_htonl(0xFFFF); + ip6.uint32[3] = ip_port.ip.ip4.uint32; + fill_addr6(ip6, &addr6->sin6_addr); + + addr6->sin6_flowinfo = 0; + addr6->sin6_scope_id = 0; + } else { + struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr; + + addrsize = sizeof(struct sockaddr_in); + addr4->sin_family = AF_INET; + fill_addr4(ip_port.ip.ip4, &addr4->sin_addr); + addr4->sin_port = ip_port.port; + } + } else if (ip_port.ip.family == TOX_AF_INET6) { + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr; + + addrsize = sizeof(struct sockaddr_in6); + addr6->sin6_family = AF_INET6; + addr6->sin6_port = ip_port.port; + fill_addr6(ip_port.ip.ip6, &addr6->sin6_addr); + + addr6->sin6_flowinfo = 0; + addr6->sin6_scope_id = 0; + } else { + /* unknown address type*/ + return -1; + } + + int res = sendto(net->sock, (const char *) data, length, 0, (struct sockaddr *)&addr, addrsize); + + loglogdata(net->log, "O=>", data, length, ip_port, res); + + return res; +} + +/* Function to receive data + * ip and port of sender is put into ip_port. + * Packet data is put into data. + * Packet length is put into length. + */ +static int receivepacket(Logger *log, Socket sock, IP_Port *ip_port, uint8_t *data, uint32_t *length) +{ + memset(ip_port, 0, sizeof(IP_Port)); + struct sockaddr_storage addr; +#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) + int addrlen = sizeof(addr); +#else + socklen_t addrlen = sizeof(addr); +#endif + *length = 0; + int fail_or_len = recvfrom(sock, (char *) data, MAX_UDP_PACKET_SIZE, 0, (struct sockaddr *)&addr, &addrlen); + + if (fail_or_len < 0) { + + if (fail_or_len < 0 && errno != EWOULDBLOCK) { + LOGGER_ERROR(log, "Unexpected error reading from socket: %u, %s\n", errno, strerror(errno)); + } + + return -1; /* Nothing received. */ + } + + *length = (uint32_t)fail_or_len; + + if (addr.ss_family == AF_INET) { + struct sockaddr_in *addr_in = (struct sockaddr_in *)&addr; + + ip_port->ip.family = make_tox_family(addr_in->sin_family); + get_ip4(&ip_port->ip.ip4, &addr_in->sin_addr); + ip_port->port = addr_in->sin_port; + } else if (addr.ss_family == AF_INET6) { + struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)&addr; + ip_port->ip.family = make_tox_family(addr_in6->sin6_family); + get_ip6(&ip_port->ip.ip6, &addr_in6->sin6_addr); + ip_port->port = addr_in6->sin6_port; + + if (IPV6_IPV4_IN_V6(ip_port->ip.ip6)) { + ip_port->ip.family = TOX_AF_INET; + ip_port->ip.ip4.uint32 = ip_port->ip.ip6.uint32[3]; + } + } else { + return -1; + } + + loglogdata(log, "=>O", data, MAX_UDP_PACKET_SIZE, *ip_port, *length); + + return 0; +} + +void networking_registerhandler(Networking_Core *net, uint8_t byte, packet_handler_callback cb, void *object) +{ + net->packethandlers[byte].function = cb; + net->packethandlers[byte].object = object; +} + +void networking_poll(Networking_Core *net, void *userdata) +{ + if (net->family == 0) { /* Socket not initialized */ + return; + } + + unix_time_update(); + + IP_Port ip_port; + uint8_t data[MAX_UDP_PACKET_SIZE]; + uint32_t length; + + while (receivepacket(net->log, net->sock, &ip_port, data, &length) != -1) { + if (length < 1) { + continue; + } + + if (!(net->packethandlers[data[0]].function)) { + LOGGER_WARNING(net->log, "[%02u] -- Packet has no handler", data[0]); + continue; + } + + net->packethandlers[data[0]].function(net->packethandlers[data[0]].object, ip_port, data, length, userdata); + } +} + +#ifndef VANILLA_NACL +/* Used for sodium_init() */ +#include <sodium.h> +#endif + +static uint8_t at_startup_ran = 0; +int networking_at_startup(void) +{ + if (at_startup_ran != 0) { + return 0; + } + +#ifndef VANILLA_NACL + +#ifdef USE_RANDOMBYTES_STIR + randombytes_stir(); +#else + + if (sodium_init() == -1) { + return -1; + } + +#endif /*USE_RANDOMBYTES_STIR*/ + +#endif/*VANILLA_NACL*/ + +#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) + WSADATA wsaData; + + if (WSAStartup(MAKEWORD(2, 2), &wsaData) != NO_ERROR) { + return -1; + } + +#endif + srand((uint32_t)current_time_actual()); + at_startup_ran = 1; + return 0; +} + +/* TODO(irungentoo): Put this somewhere */ +#if 0 +static void at_shutdown(void) +{ +#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) + WSACleanup(); +#endif +} +#endif + +/* Initialize networking. + * Added for reverse compatibility with old new_networking calls. + */ +Networking_Core *new_networking(Logger *log, IP ip, uint16_t port) +{ + return new_networking_ex(log, ip, port, port + (TOX_PORTRANGE_TO - TOX_PORTRANGE_FROM), 0); +} + +/* Initialize networking. + * Bind to ip and port. + * ip must be in network order EX: 127.0.0.1 = (7F000001). + * port is in host byte order (this means don't worry about it). + * + * return Networking_Core object if no problems + * return NULL if there are problems. + * + * If error is non NULL it is set to 0 if no issues, 1 if socket related error, 2 if other. + */ +Networking_Core *new_networking_ex(Logger *log, IP ip, uint16_t port_from, uint16_t port_to, unsigned int *error) +{ + /* If both from and to are 0, use default port range + * If one is 0 and the other is non-0, use the non-0 value as only port + * If from > to, swap + */ + if (port_from == 0 && port_to == 0) { + port_from = TOX_PORTRANGE_FROM; + port_to = TOX_PORTRANGE_TO; + } else if (port_from == 0 && port_to != 0) { + port_from = port_to; + } else if (port_from != 0 && port_to == 0) { + port_to = port_from; + } else if (port_from > port_to) { + uint16_t temp = port_from; + port_from = port_to; + port_to = temp; + } + + if (error) { + *error = 2; + } + + /* maybe check for invalid IPs like 224+.x.y.z? if there is any IP set ever */ + if (ip.family != TOX_AF_INET && ip.family != TOX_AF_INET6) { + LOGGER_ERROR(log, "Invalid address family: %u\n", ip.family); + return NULL; + } + + if (networking_at_startup() != 0) { + return NULL; + } + + Networking_Core *temp = (Networking_Core *)calloc(1, sizeof(Networking_Core)); + + if (temp == NULL) { + return NULL; + } + + temp->log = log; + temp->family = ip.family; + temp->port = 0; + + /* Initialize our socket. */ + /* add log message what we're creating */ + temp->sock = net_socket(temp->family, TOX_SOCK_DGRAM, TOX_PROTO_UDP); + + /* Check for socket error. */ + if (!sock_valid(temp->sock)) { + LOGGER_ERROR(log, "Failed to get a socket?! %u, %s\n", errno, strerror(errno)); + free(temp); + + if (error) { + *error = 1; + } + + return NULL; + } + + /* Functions to increase the size of the send and receive UDP buffers. + */ + int n = 1024 * 1024 * 2; + setsockopt(temp->sock, SOL_SOCKET, SO_RCVBUF, (const char *)&n, sizeof(n)); + setsockopt(temp->sock, SOL_SOCKET, SO_SNDBUF, (const char *)&n, sizeof(n)); + + /* Enable broadcast on socket */ + int broadcast = 1; + setsockopt(temp->sock, SOL_SOCKET, SO_BROADCAST, (const char *)&broadcast, sizeof(broadcast)); + + /* iOS UDP sockets are weird and apparently can SIGPIPE */ + if (!set_socket_nosigpipe(temp->sock)) { + kill_networking(temp); + + if (error) { + *error = 1; + } + + return NULL; + } + + /* Set socket nonblocking. */ + if (!set_socket_nonblock(temp->sock)) { + kill_networking(temp); + + if (error) { + *error = 1; + } + + return NULL; + } + + /* Bind our socket to port PORT and the given IP address (usually 0.0.0.0 or ::) */ + uint16_t *portptr = NULL; + struct sockaddr_storage addr; + size_t addrsize; + + memset(&addr, 0, sizeof(struct sockaddr_storage)); + + if (temp->family == TOX_AF_INET) { + struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr; + + addrsize = sizeof(struct sockaddr_in); + addr4->sin_family = AF_INET; + addr4->sin_port = 0; + fill_addr4(ip.ip4, &addr4->sin_addr); + + portptr = &addr4->sin_port; + } else if (temp->family == TOX_AF_INET6) { + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr; + + addrsize = sizeof(struct sockaddr_in6); + addr6->sin6_family = AF_INET6; + addr6->sin6_port = 0; + fill_addr6(ip.ip6, &addr6->sin6_addr); + + addr6->sin6_flowinfo = 0; + addr6->sin6_scope_id = 0; + + portptr = &addr6->sin6_port; + } else { + free(temp); + return NULL; + } + + if (ip.family == TOX_AF_INET6) { + int is_dualstack = set_socket_dualstack(temp->sock); + LOGGER_DEBUG(log, "Dual-stack socket: %s", + is_dualstack ? "enabled" : "Failed to enable, won't be able to receive from/send to IPv4 addresses"); + /* multicast local nodes */ + struct ipv6_mreq mreq; + memset(&mreq, 0, sizeof(mreq)); + mreq.ipv6mr_multiaddr.s6_addr[ 0] = 0xFF; + mreq.ipv6mr_multiaddr.s6_addr[ 1] = 0x02; + mreq.ipv6mr_multiaddr.s6_addr[15] = 0x01; + mreq.ipv6mr_interface = 0; + int res = setsockopt(temp->sock, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, (const char *)&mreq, sizeof(mreq)); + + LOGGER_DEBUG(log, res < 0 ? "Failed to activate local multicast membership. (%u, %s)" : + "Local multicast group FF02::1 joined successfully", errno, strerror(errno)); + } + + /* a hanging program or a different user might block the standard port; + * as long as it isn't a parameter coming from the commandline, + * try a few ports after it, to see if we can find a "free" one + * + * if we go on without binding, the first sendto() automatically binds to + * a free port chosen by the system (i.e. anything from 1024 to 65535) + * + * returning NULL after bind fails has both advantages and disadvantages: + * advantage: + * we can rely on getting the port in the range 33445..33450, which + * enables us to tell joe user to open their firewall to a small range + * + * disadvantage: + * some clients might not test return of tox_new(), blindly assuming that + * it worked ok (which it did previously without a successful bind) + */ + uint16_t port_to_try = port_from; + *portptr = net_htons(port_to_try); + int tries; + + for (tries = port_from; tries <= port_to; tries++) { + int res = bind(temp->sock, (struct sockaddr *)&addr, addrsize); + + if (!res) { + temp->port = *portptr; + + char ip_str[IP_NTOA_LEN]; + LOGGER_DEBUG(log, "Bound successfully to %s:%u", ip_ntoa(&ip, ip_str, sizeof(ip_str)), + net_ntohs(temp->port)); + + /* errno isn't reset on success, only set on failure, the failed + * binds with parallel clients yield a -EPERM to the outside if + * errno isn't cleared here */ + if (tries > 0) { + errno = 0; + } + + if (error) { + *error = 0; + } + + return temp; + } + + port_to_try++; + + if (port_to_try > port_to) { + port_to_try = port_from; + } + + *portptr = net_htons(port_to_try); + } + + char ip_str[IP_NTOA_LEN]; + LOGGER_ERROR(log, "Failed to bind socket: %u, %s IP: %s port_from: %u port_to: %u", errno, strerror(errno), + ip_ntoa(&ip, ip_str, sizeof(ip_str)), port_from, port_to); + + kill_networking(temp); + + if (error) { + *error = 1; + } + + return NULL; +} + +/* Function to cleanup networking stuff. */ +void kill_networking(Networking_Core *net) +{ + if (!net) { + return; + } + + if (net->family != 0) { /* Socket not initialized */ + kill_sock(net->sock); + } + + free(net); +} + + +/* ip_equal + * compares two IPAny structures + * unset means unequal + * + * returns 0 when not equal or when uninitialized + */ +int ip_equal(const IP *a, const IP *b) +{ + if (!a || !b) { + return 0; + } + + /* same family */ + if (a->family == b->family) { + if (a->family == TOX_AF_INET || a->family == TCP_INET) { + struct in_addr addr_a; + struct in_addr addr_b; + fill_addr4(a->ip4, &addr_a); + fill_addr4(b->ip4, &addr_b); + return addr_a.s_addr == addr_b.s_addr; + } + + if (a->family == TOX_AF_INET6 || a->family == TCP_INET6) { + return a->ip6.uint64[0] == b->ip6.uint64[0] && + a->ip6.uint64[1] == b->ip6.uint64[1]; + } + + return 0; + } + + /* different family: check on the IPv6 one if it is the IPv4 one embedded */ + if ((a->family == TOX_AF_INET) && (b->family == TOX_AF_INET6)) { + if (IPV6_IPV4_IN_V6(b->ip6)) { + struct in_addr addr_a; + fill_addr4(a->ip4, &addr_a); + return addr_a.s_addr == b->ip6.uint32[3]; + } + } else if ((a->family == TOX_AF_INET6) && (b->family == TOX_AF_INET)) { + if (IPV6_IPV4_IN_V6(a->ip6)) { + struct in_addr addr_b; + fill_addr4(b->ip4, &addr_b); + return a->ip6.uint32[3] == addr_b.s_addr; + } + } + + return 0; +} + +/* ipport_equal + * compares two IPAny_Port structures + * unset means unequal + * + * returns 0 when not equal or when uninitialized + */ +int ipport_equal(const IP_Port *a, const IP_Port *b) +{ + if (!a || !b) { + return 0; + } + + if (!a->port || (a->port != b->port)) { + return 0; + } + + return ip_equal(&a->ip, &b->ip); +} + +/* nulls out ip */ +void ip_reset(IP *ip) +{ + if (!ip) { + return; + } + + memset(ip, 0, sizeof(IP)); +} + +/* nulls out ip, sets family according to flag */ +void ip_init(IP *ip, uint8_t ipv6enabled) +{ + if (!ip) { + return; + } + + memset(ip, 0, sizeof(IP)); + ip->family = ipv6enabled ? TOX_AF_INET6 : TOX_AF_INET; +} + +/* checks if ip is valid */ +int ip_isset(const IP *ip) +{ + if (!ip) { + return 0; + } + + return (ip->family != 0); +} + +/* checks if ip is valid */ +int ipport_isset(const IP_Port *ipport) +{ + if (!ipport) { + return 0; + } + + if (!ipport->port) { + return 0; + } + + return ip_isset(&ipport->ip); +} + +/* copies an ip structure (careful about direction!) */ +void ip_copy(IP *target, const IP *source) +{ + if (!source || !target) { + return; + } + + memcpy(target, source, sizeof(IP)); +} + +/* copies an ip_port structure (careful about direction!) */ +void ipport_copy(IP_Port *target, const IP_Port *source) +{ + if (!source || !target) { + return; + } + + memcpy(target, source, sizeof(IP_Port)); +} + +/* ip_ntoa + * converts ip into a string + * ip_str must be of length at least IP_NTOA_LEN + * + * IPv6 addresses are enclosed into square brackets, i.e. "[IPv6]" + * writes error message into the buffer on error + * + * returns ip_str + */ +const char *ip_ntoa(const IP *ip, char *ip_str, size_t length) +{ + if (length < IP_NTOA_LEN) { + snprintf(ip_str, length, "Bad buf length"); + return ip_str; + } + + if (ip) { + const int family = make_family(ip->family); + + if (ip->family == TOX_AF_INET) { + /* returns standard quad-dotted notation */ + struct in_addr addr; + fill_addr4(ip->ip4, &addr); + + ip_str[0] = 0; + inet_ntop(family, &addr, ip_str, length); + } else if (ip->family == TOX_AF_INET6) { + /* returns hex-groups enclosed into square brackets */ + struct in6_addr addr; + fill_addr6(ip->ip6, &addr); + + ip_str[0] = '['; + inet_ntop(family, &addr, &ip_str[1], length - 3); + size_t len = strlen(ip_str); + ip_str[len] = ']'; + ip_str[len + 1] = 0; + } else { + snprintf(ip_str, length, "(IP invalid, family %u)", ip->family); + } + } else { + snprintf(ip_str, length, "(IP invalid: NULL)"); + } + + /* brute force protection against lacking termination */ + ip_str[length - 1] = 0; + return ip_str; +} + +/* + * ip_parse_addr + * parses IP structure into an address string + * + * input + * ip: ip of TOX_AF_INET or TOX_AF_INET6 families + * length: length of the address buffer + * Must be at least INET_ADDRSTRLEN for TOX_AF_INET + * and INET6_ADDRSTRLEN for TOX_AF_INET6 + * + * output + * address: dotted notation (IPv4: quad, IPv6: 16) or colon notation (IPv6) + * + * returns 1 on success, 0 on failure + */ +int ip_parse_addr(const IP *ip, char *address, size_t length) +{ + if (!address || !ip) { + return 0; + } + + if (ip->family == TOX_AF_INET) { + const struct in_addr *addr = (const struct in_addr *)&ip->ip4; + return inet_ntop(ip->family, addr, address, length) != NULL; + } + + if (ip->family == TOX_AF_INET6) { + const struct in6_addr *addr = (const struct in6_addr *)&ip->ip6; + return inet_ntop(ip->family, addr, address, length) != NULL; + } + + return 0; +} + +/* + * addr_parse_ip + * directly parses the input into an IP structure + * tries IPv4 first, then IPv6 + * + * input + * address: dotted notation (IPv4: quad, IPv6: 16) or colon notation (IPv6) + * + * output + * IP: family and the value is set on success + * + * returns 1 on success, 0 on failure + */ +int addr_parse_ip(const char *address, IP *to) +{ + if (!address || !to) { + return 0; + } + + struct in_addr addr4; + + if (inet_pton(AF_INET, address, &addr4) == 1) { + to->family = TOX_AF_INET; + get_ip4(&to->ip4, &addr4); + return 1; + } + + struct in6_addr addr6; + + if (inet_pton(AF_INET6, address, &addr6) == 1) { + to->family = TOX_AF_INET6; + get_ip6(&to->ip6, &addr6); + return 1; + } + + return 0; +} + +/* + * addr_resolve(): + * uses getaddrinfo to resolve an address into an IP address + * uses the first IPv4/IPv6 addresses returned by getaddrinfo + * + * input + * address: a hostname (or something parseable to an IP address) + * to: to.family MUST be initialized, either set to a specific IP version + * (TOX_AF_INET/TOX_AF_INET6) or to the unspecified AF_UNSPEC (= 0), if both + * IP versions are acceptable + * extra can be NULL and is only set in special circumstances, see returns + * + * returns in *to a valid IPAny (v4/v6), + * prefers v6 if ip.family was AF_UNSPEC and both available + * returns in *extra an IPv4 address, if family was AF_UNSPEC and *to is TOX_AF_INET6 + * returns 0 on failure, TOX_ADDR_RESOLVE_* on success. + */ +int addr_resolve(const char *address, IP *to, IP *extra) +{ + if (!address || !to) { + return 0; + } + + Family tox_family = to->family; + Family family = make_family(tox_family); + + struct addrinfo *server = NULL; + struct addrinfo *walker = NULL; + struct addrinfo hints; + int rc; + int result = 0; + int done = 0; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = family; + hints.ai_socktype = SOCK_DGRAM; // type of socket Tox uses. + + if (networking_at_startup() != 0) { + return 0; + } + + rc = getaddrinfo(address, NULL, &hints, &server); + + // Lookup failed. + if (rc != 0) { + return 0; + } + + IP ip4; + ip_init(&ip4, 0); // ipv6enabled = 0 + IP ip6; + ip_init(&ip6, 1); // ipv6enabled = 1 + + for (walker = server; (walker != NULL) && !done; walker = walker->ai_next) { + switch (walker->ai_family) { + case AF_INET: + if (walker->ai_family == family) { /* AF_INET requested, done */ + struct sockaddr_in *addr = (struct sockaddr_in *)walker->ai_addr; + get_ip4(&to->ip4, &addr->sin_addr); + result = TOX_ADDR_RESOLVE_INET; + done = 1; + } else if (!(result & TOX_ADDR_RESOLVE_INET)) { /* AF_UNSPEC requested, store away */ + struct sockaddr_in *addr = (struct sockaddr_in *)walker->ai_addr; + get_ip4(&ip4.ip4, &addr->sin_addr); + result |= TOX_ADDR_RESOLVE_INET; + } + + break; /* switch */ + + case AF_INET6: + if (walker->ai_family == family) { /* AF_INET6 requested, done */ + if (walker->ai_addrlen == sizeof(struct sockaddr_in6)) { + struct sockaddr_in6 *addr = (struct sockaddr_in6 *)walker->ai_addr; + get_ip6(&to->ip6, &addr->sin6_addr); + result = TOX_ADDR_RESOLVE_INET6; + done = 1; + } + } else if (!(result & TOX_ADDR_RESOLVE_INET6)) { /* AF_UNSPEC requested, store away */ + if (walker->ai_addrlen == sizeof(struct sockaddr_in6)) { + struct sockaddr_in6 *addr = (struct sockaddr_in6 *)walker->ai_addr; + get_ip6(&ip6.ip6, &addr->sin6_addr); + result |= TOX_ADDR_RESOLVE_INET6; + } + } + + break; /* switch */ + } + } + + if (family == AF_UNSPEC) { + if (result & TOX_ADDR_RESOLVE_INET6) { + ip_copy(to, &ip6); + + if ((result & TOX_ADDR_RESOLVE_INET) && (extra != NULL)) { + ip_copy(extra, &ip4); + } + } else if (result & TOX_ADDR_RESOLVE_INET) { + ip_copy(to, &ip4); + } else { + result = 0; + } + } + + freeaddrinfo(server); + return result; +} + +/* + * addr_resolve_or_parse_ip + * resolves string into an IP address + * + * address: a hostname (or something parseable to an IP address) + * to: to.family MUST be initialized, either set to a specific IP version + * (TOX_AF_INET/TOX_AF_INET6) or to the unspecified AF_UNSPEC (= 0), if both + * IP versions are acceptable + * extra can be NULL and is only set in special circumstances, see returns + * + * returns in *tro a matching address (IPv6 or IPv4) + * returns in *extra, if not NULL, an IPv4 address, if to->family was AF_UNSPEC + * returns 1 on success + * returns 0 on failure + */ +int addr_resolve_or_parse_ip(const char *address, IP *to, IP *extra) +{ + if (!addr_resolve(address, to, extra)) { + if (!addr_parse_ip(address, to)) { + return 0; + } + } + + return 1; +} + +int net_connect(Socket sock, IP_Port ip_port) +{ + struct sockaddr_storage addr = {0}; + size_t addrsize; + + if (ip_port.ip.family == TOX_AF_INET) { + struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr; + + addrsize = sizeof(struct sockaddr_in); + addr4->sin_family = AF_INET; + fill_addr4(ip_port.ip.ip4, &addr4->sin_addr); + addr4->sin_port = ip_port.port; + } else if (ip_port.ip.family == TOX_AF_INET6) { + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr; + + addrsize = sizeof(struct sockaddr_in6); + addr6->sin6_family = AF_INET6; + fill_addr6(ip_port.ip.ip6, &addr6->sin6_addr); + addr6->sin6_port = ip_port.port; + } else { + return 0; + } + + return connect(sock, (struct sockaddr *)&addr, addrsize); +} + +int32_t net_getipport(const char *node, IP_Port **res, int tox_type) +{ + struct addrinfo *infos; + int ret = getaddrinfo(node, NULL, NULL, &infos); + *res = NULL; + + if (ret != 0) { + return -1; + } + + // Used to avoid malloc parameter overflow + const size_t MAX_COUNT = MIN(SIZE_MAX, INT32_MAX) / sizeof(IP_Port); + int type = make_socktype(tox_type); + struct addrinfo *cur; + int32_t count = 0; + + for (cur = infos; count < MAX_COUNT && cur != NULL; cur = cur->ai_next) { + if (cur->ai_socktype && type > 0 && cur->ai_socktype != type) { + continue; + } + + if (cur->ai_family != AF_INET && cur->ai_family != AF_INET6) { + continue; + } + + count++; + } + + assert(count <= MAX_COUNT); + + if (count == 0) { + return 0; + } + + *res = (IP_Port *)malloc(sizeof(IP_Port) * count); + + if (*res == NULL) { + return -1; + } + + IP_Port *ip_port = *res; + + for (cur = infos; cur != NULL; cur = cur->ai_next) { + if (cur->ai_socktype && type > 0 && cur->ai_socktype != type) { + continue; + } + + if (cur->ai_family == AF_INET) { + struct sockaddr_in *addr = (struct sockaddr_in *)cur->ai_addr; + memcpy(&ip_port->ip.ip4, &addr->sin_addr, sizeof(IP4)); + } else if (cur->ai_family == AF_INET6) { + struct sockaddr_in6 *addr = (struct sockaddr_in6 *)cur->ai_addr; + memcpy(&ip_port->ip.ip6, &addr->sin6_addr, sizeof(IP6)); + } else { + continue; + } + + ip_port->ip.family = make_tox_family(cur->ai_family); + + ip_port++; + } + + freeaddrinfo(infos); + + return count; +} + +void net_freeipport(IP_Port *ip_ports) +{ + free(ip_ports); +} + +/* return 1 on success + * return 0 on failure + */ +int bind_to_port(Socket sock, int family, uint16_t port) +{ + struct sockaddr_storage addr = {0}; + size_t addrsize; + + if (family == TOX_AF_INET) { + struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr; + + addrsize = sizeof(struct sockaddr_in); + addr4->sin_family = AF_INET; + addr4->sin_port = net_htons(port); + } else if (family == TOX_AF_INET6) { + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr; + + addrsize = sizeof(struct sockaddr_in6); + addr6->sin6_family = AF_INET6; + addr6->sin6_port = net_htons(port); + } else { + return 0; + } + + return (bind(sock, (struct sockaddr *)&addr, addrsize) == 0); +} + +static int make_tox_family(int family) +{ + switch (family) { + case AF_INET: + return TOX_AF_INET; + + case AF_INET6: + return TOX_AF_INET6; + + case AF_UNSPEC: + return TOX_AF_UNSPEC; + + default: + return family; + } +} + +static int make_family(int tox_family) +{ + switch (tox_family) { + case TOX_AF_INET: + return AF_INET; + + case TOX_AF_INET6: + return AF_INET6; + + case TOX_AF_UNSPEC: + return AF_UNSPEC; + + default: + return tox_family; + } +} + +static int make_socktype(int type) +{ + switch (type) { + case TOX_SOCK_STREAM: + return SOCK_STREAM; + + case TOX_SOCK_DGRAM: + return SOCK_DGRAM; + + default: + return type; + } +} + +static int make_proto(int proto) +{ + switch (proto) { + case TOX_PROTO_TCP: + return IPPROTO_TCP; + + case TOX_PROTO_UDP: + return IPPROTO_UDP; + + default: + return proto; + } +} + +Socket net_socket(int domain, int type, int protocol) +{ + int platform_domain = make_family(domain); + int platform_type = make_socktype(type); + int platform_prot = make_proto(protocol); + return socket(platform_domain, platform_type, platform_prot); +} + +/* TODO: Remove, when tox DNS support will be removed. + * Used only by dns3_test.c + */ +size_t net_sendto_ip4(Socket sock, const char *buf, size_t n, IP_Port ip_port) +{ + struct sockaddr_in target; + size_t addrsize = sizeof(target); + target.sin_family = make_family(ip_port.ip.family); + target.sin_port = net_htons(ip_port.port); + fill_addr4(ip_port.ip.ip4, &target.sin_addr); + + return (size_t)sendto(sock, buf, n, 0, (struct sockaddr *)&target, addrsize); +} + +uint32_t net_htonl(uint32_t hostlong) +{ + return htonl(hostlong); +} + +uint16_t net_htons(uint16_t hostshort) +{ + return htons(hostshort); +} + +uint32_t net_ntohl(uint32_t hostlong) +{ + return ntohl(hostlong); +} + +uint16_t net_ntohs(uint16_t hostshort) +{ + return ntohs(hostshort); +} diff --git a/libs/libtox/src/toxcore/network.h b/libs/libtox/src/toxcore/network.h new file mode 100644 index 0000000000..0b9da5a40f --- /dev/null +++ b/libs/libtox/src/toxcore/network.h @@ -0,0 +1,424 @@ +/* + * Datatypes, functions and includes for the core networking. + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2013 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifndef NETWORK_H +#define NETWORK_H + +#ifdef PLAN9 +#include <u.h> // Plan 9 requires this is imported first +// Comment line here to avoid reordering by source code formatters. +#include <libc.h> +#endif + +#include "ccompat.h" +#include "logger.h" + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) /* Put win32 includes here */ +#ifndef WINVER +//Windows XP +#define WINVER 0x0501 +#endif + +// The mingw32/64 Windows library warns about including winsock2.h after +// windows.h even though with the above it's a valid thing to do. So, to make +// mingw32 headers happy, we include winsock2.h first. +#include <winsock2.h> + +#include <windows.h> +#include <ws2tcpip.h> + +#else // UNIX includes + +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <unistd.h> + +#endif + +typedef short Family; + +typedef int Socket; +Socket net_socket(int domain, int type, int protocol); + +#define MAX_UDP_PACKET_SIZE 2048 + +typedef enum NET_PACKET_TYPE { + NET_PACKET_PING_REQUEST = 0x00, /* Ping request packet ID. */ + NET_PACKET_PING_RESPONSE = 0x01, /* Ping response packet ID. */ + NET_PACKET_GET_NODES = 0x02, /* Get nodes request packet ID. */ + NET_PACKET_SEND_NODES_IPV6 = 0x04, /* Send nodes response packet ID for other addresses. */ + NET_PACKET_COOKIE_REQUEST = 0x18, /* Cookie request packet */ + NET_PACKET_COOKIE_RESPONSE = 0x19, /* Cookie response packet */ + NET_PACKET_CRYPTO_HS = 0x1a, /* Crypto handshake packet */ + NET_PACKET_CRYPTO_DATA = 0x1b, /* Crypto data packet */ + NET_PACKET_CRYPTO = 0x20, /* Encrypted data packet ID. */ + NET_PACKET_LAN_DISCOVERY = 0x21, /* LAN discovery packet ID. */ + + /* See: docs/Prevent_Tracking.txt and onion.{c,h} */ + NET_PACKET_ONION_SEND_INITIAL = 0x80, + NET_PACKET_ONION_SEND_1 = 0x81, + NET_PACKET_ONION_SEND_2 = 0x82, + + NET_PACKET_ANNOUNCE_REQUEST = 0x83, + NET_PACKET_ANNOUNCE_RESPONSE = 0x84, + NET_PACKET_ONION_DATA_REQUEST = 0x85, + NET_PACKET_ONION_DATA_RESPONSE = 0x86, + + NET_PACKET_ONION_RECV_3 = 0x8c, + NET_PACKET_ONION_RECV_2 = 0x8d, + NET_PACKET_ONION_RECV_1 = 0x8e, + + BOOTSTRAP_INFO_PACKET_ID = 0xf0, /* Only used for bootstrap nodes */ + + NET_PACKET_MAX = 0xff, /* This type must remain within a single uint8. */ +} NET_PACKET_TYPE; + + +#define TOX_PORTRANGE_FROM 33445 +#define TOX_PORTRANGE_TO 33545 +#define TOX_PORT_DEFAULT TOX_PORTRANGE_FROM + +/* Redefinitions of variables for safe transfer over wire. */ +#define TOX_AF_UNSPEC 0 +#define TOX_AF_INET 2 +#define TOX_AF_INET6 10 +#define TOX_TCP_INET 130 +#define TOX_TCP_INET6 138 + +#define TOX_SOCK_STREAM 1 +#define TOX_SOCK_DGRAM 2 + +#define TOX_PROTO_TCP 1 +#define TOX_PROTO_UDP 2 + +/* TCP related */ +#define TCP_ONION_FAMILY (TOX_AF_INET6 + 1) +#define TCP_INET (TOX_AF_INET6 + 2) +#define TCP_INET6 (TOX_AF_INET6 + 3) +#define TCP_FAMILY (TOX_AF_INET6 + 4) + +typedef union { + uint32_t uint32; + uint16_t uint16[2]; + uint8_t uint8[4]; +} +IP4; + +IP4 get_ip4_loopback(void); +extern const IP4 IP4_BROADCAST; + +typedef union { + uint8_t uint8[16]; + uint16_t uint16[8]; + uint32_t uint32[4]; + uint64_t uint64[2]; +} +IP6; + +IP6 get_ip6_loopback(void); +extern const IP6 IP6_BROADCAST; + +typedef struct { + uint8_t family; + GNU_EXTENSION union { + IP4 ip4; + IP6 ip6; + }; +} +IP; + +typedef struct { + IP ip; + uint16_t port; +} +IP_Port; + +/* Convert values between host and network byte order. + */ +uint32_t net_htonl(uint32_t hostlong); +uint16_t net_htons(uint16_t hostshort); +uint32_t net_ntohl(uint32_t hostlong); +uint16_t net_ntohs(uint16_t hostshort); + +/* Does the IP6 struct a contain an IPv4 address in an IPv6 one? */ +#define IPV6_IPV4_IN_V6(a) ((a.uint64[0] == 0) && (a.uint32[2] == net_htonl (0xffff))) + +#define SIZE_IP4 4 +#define SIZE_IP6 16 +#define SIZE_IP (1 + SIZE_IP6) +#define SIZE_PORT 2 +#define SIZE_IPPORT (SIZE_IP + SIZE_PORT) + +#define TOX_ENABLE_IPV6_DEFAULT 1 + +/* addr_resolve return values */ +#define TOX_ADDR_RESOLVE_INET 1 +#define TOX_ADDR_RESOLVE_INET6 2 + +#define TOX_INET6_ADDRSTRLEN 66 +#define TOX_INET_ADDRSTRLEN 22 + +/* ip_ntoa + * converts ip into a string + * ip_str must be of length at least IP_NTOA_LEN + * + * IPv6 addresses are enclosed into square brackets, i.e. "[IPv6]" + * writes error message into the buffer on error + * + * returns ip_str + */ +/* this would be TOX_INET6_ADDRSTRLEN, but it might be too short for the error message */ +#define IP_NTOA_LEN 96 // TODO(irungentoo): magic number. Why not INET6_ADDRSTRLEN ? +const char *ip_ntoa(const IP *ip, char *ip_str, size_t length); + +/* + * ip_parse_addr + * parses IP structure into an address string + * + * input + * ip: ip of TOX_AF_INET or TOX_AF_INET6 families + * length: length of the address buffer + * Must be at least TOX_INET_ADDRSTRLEN for TOX_AF_INET + * and TOX_INET6_ADDRSTRLEN for TOX_AF_INET6 + * + * output + * address: dotted notation (IPv4: quad, IPv6: 16) or colon notation (IPv6) + * + * returns 1 on success, 0 on failure + */ +int ip_parse_addr(const IP *ip, char *address, size_t length); + +/* + * addr_parse_ip + * directly parses the input into an IP structure + * tries IPv4 first, then IPv6 + * + * input + * address: dotted notation (IPv4: quad, IPv6: 16) or colon notation (IPv6) + * + * output + * IP: family and the value is set on success + * + * returns 1 on success, 0 on failure + */ +int addr_parse_ip(const char *address, IP *to); + +/* ip_equal + * compares two IPAny structures + * unset means unequal + * + * returns 0 when not equal or when uninitialized + */ +int ip_equal(const IP *a, const IP *b); + +/* ipport_equal + * compares two IPAny_Port structures + * unset means unequal + * + * returns 0 when not equal or when uninitialized + */ +int ipport_equal(const IP_Port *a, const IP_Port *b); + +/* nulls out ip */ +void ip_reset(IP *ip); +/* nulls out ip, sets family according to flag */ +void ip_init(IP *ip, uint8_t ipv6enabled); +/* checks if ip is valid */ +int ip_isset(const IP *ip); +/* checks if ip is valid */ +int ipport_isset(const IP_Port *ipport); +/* copies an ip structure */ +void ip_copy(IP *target, const IP *source); +/* copies an ip_port structure */ +void ipport_copy(IP_Port *target, const IP_Port *source); + +/* + * addr_resolve(): + * uses getaddrinfo to resolve an address into an IP address + * uses the first IPv4/IPv6 addresses returned by getaddrinfo + * + * input + * address: a hostname (or something parseable to an IP address) + * to: to.family MUST be initialized, either set to a specific IP version + * (TOX_AF_INET/TOX_AF_INET6) or to the unspecified TOX_AF_UNSPEC (= 0), if both + * IP versions are acceptable + * extra can be NULL and is only set in special circumstances, see returns + * + * returns in *to a valid IPAny (v4/v6), + * prefers v6 if ip.family was TOX_AF_UNSPEC and both available + * returns in *extra an IPv4 address, if family was TOX_AF_UNSPEC and *to is TOX_AF_INET6 + * returns 0 on failure + */ +int addr_resolve(const char *address, IP *to, IP *extra); + +/* + * addr_resolve_or_parse_ip + * resolves string into an IP address + * + * address: a hostname (or something parseable to an IP address) + * to: to.family MUST be initialized, either set to a specific IP version + * (TOX_AF_INET/TOX_AF_INET6) or to the unspecified TOX_AF_UNSPEC (= 0), if both + * IP versions are acceptable + * extra can be NULL and is only set in special circumstances, see returns + * + * returns in *tro a matching address (IPv6 or IPv4) + * returns in *extra, if not NULL, an IPv4 address, if to->family was TOX_AF_UNSPEC + * returns 1 on success + * returns 0 on failure + */ +int addr_resolve_or_parse_ip(const char *address, IP *to, IP *extra); + +/* Function to receive data, ip and port of sender is put into ip_port. + * Packet data is put into data. + * Packet length is put into length. + */ +typedef int (*packet_handler_callback)(void *object, IP_Port ip_port, const uint8_t *data, uint16_t len, + void *userdata); + +typedef struct { + packet_handler_callback function; + void *object; +} Packet_Handles; + +typedef struct { + Logger *log; + Packet_Handles packethandlers[256]; + + Family family; + uint16_t port; + /* Our UDP socket. */ + Socket sock; +} Networking_Core; + +/* Run this before creating sockets. + * + * return 0 on success + * return -1 on failure + */ +int networking_at_startup(void); + +/* Check if socket is valid. + * + * return 1 if valid + * return 0 if not valid + */ +int sock_valid(Socket sock); + +/* Close the socket. + */ +void kill_sock(Socket sock); + +/* Set socket as nonblocking + * + * return 1 on success + * return 0 on failure + */ +int set_socket_nonblock(Socket sock); + +/* Set socket to not emit SIGPIPE + * + * return 1 on success + * return 0 on failure + */ +int set_socket_nosigpipe(Socket sock); + +/* Enable SO_REUSEADDR on socket. + * + * return 1 on success + * return 0 on failure + */ +int set_socket_reuseaddr(Socket sock); + +/* Set socket to dual (IPv4 + IPv6 socket) + * + * return 1 on success + * return 0 on failure + */ +int set_socket_dualstack(Socket sock); + +/* return current monotonic time in milliseconds (ms). */ +uint64_t current_time_monotonic(void); + +/* Basic network functions: */ + +/* Function to send packet(data) of length length to ip_port. */ +int sendpacket(Networking_Core *net, IP_Port ip_port, const uint8_t *data, uint16_t length); + +/* Function to call when packet beginning with byte is received. */ +void networking_registerhandler(Networking_Core *net, uint8_t byte, packet_handler_callback cb, void *object); + +/* Call this several times a second. */ +void networking_poll(Networking_Core *net, void *userdata); + +/* Connect a socket to the address specified by the ip_port. */ +int net_connect(Socket sock, IP_Port ip_port); + +/* High-level getaddrinfo implementation. + * Given node, which identifies an Internet host, net_getipport() fills an array + * with one or more IP_Port structures, each of which contains an Internet + * address that can be specified by calling net_connect(), the port is ignored. + * + * Skip all addresses with socktype != type (use type = -1 to get all addresses) + * To correctly deallocate array memory use net_freeipport() + * + * return number of elements in res array + * and -1 on error. + */ +int32_t net_getipport(const char *node, IP_Port **res, int tox_type); + +/* Deallocates memory allocated by net_getipport + */ +void net_freeipport(IP_Port *ip_ports); + +/* return 1 on success + * return 0 on failure + */ +int bind_to_port(Socket sock, int family, uint16_t port); + +size_t net_sendto_ip4(Socket sock, const char *buf, size_t n, IP_Port ip_port); + +/* Initialize networking. + * bind to ip and port. + * ip must be in network order EX: 127.0.0.1 = (7F000001). + * port is in host byte order (this means don't worry about it). + * + * return Networking_Core object if no problems + * return NULL if there are problems. + * + * If error is non NULL it is set to 0 if no issues, 1 if socket related error, 2 if other. + */ +Networking_Core *new_networking(Logger *log, IP ip, uint16_t port); +Networking_Core *new_networking_ex(Logger *log, IP ip, uint16_t port_from, uint16_t port_to, unsigned int *error); + +/* Function to cleanup networking stuff (doesn't do much right now). */ +void kill_networking(Networking_Core *net); + +#endif diff --git a/libs/libtox/src/toxcore/onion.c b/libs/libtox/src/toxcore/onion.c new file mode 100644 index 0000000000..fbaf7205d9 --- /dev/null +++ b/libs/libtox/src/toxcore/onion.c @@ -0,0 +1,679 @@ +/* + * Implementation of the onion part of docs/Prevent_Tracking.txt + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2013 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "onion.h" + +#include "util.h" + +#define RETURN_1 ONION_RETURN_1 +#define RETURN_2 ONION_RETURN_2 +#define RETURN_3 ONION_RETURN_3 + +#define SEND_BASE ONION_SEND_BASE +#define SEND_3 ONION_SEND_3 +#define SEND_2 ONION_SEND_2 +#define SEND_1 ONION_SEND_1 + +/* Change symmetric keys every 2 hours to make paths expire eventually. */ +#define KEY_REFRESH_INTERVAL (2 * 60 * 60) +static void change_symmetric_key(Onion *onion) +{ + if (is_timeout(onion->timestamp, KEY_REFRESH_INTERVAL)) { + new_symmetric_key(onion->secret_symmetric_key); + onion->timestamp = unix_time(); + } +} + +/* packing and unpacking functions */ +static void ip_pack(uint8_t *data, IP source) +{ + data[0] = source.family; + + if (source.family == TOX_AF_INET || source.family == TOX_TCP_INET) { + memset(data + 1, 0, SIZE_IP6); + memcpy(data + 1, source.ip4.uint8, SIZE_IP4); + } else { + memcpy(data + 1, source.ip6.uint8, SIZE_IP6); + } +} + +/* return 0 on success, -1 on failure. */ +static int ip_unpack(IP *target, const uint8_t *data, unsigned int data_size, bool disable_family_check) +{ + if (data_size < (1 + SIZE_IP6)) { + return -1; + } + + target->family = data[0]; + + if (target->family == TOX_AF_INET || target->family == TOX_TCP_INET) { + memcpy(target->ip4.uint8, data + 1, SIZE_IP4); + } else { + memcpy(target->ip6.uint8, data + 1, SIZE_IP6); + } + + bool valid = disable_family_check || + target->family == TOX_AF_INET || + target->family == TOX_AF_INET6; + + return valid ? 0 : -1; +} + +static void ipport_pack(uint8_t *data, const IP_Port *source) +{ + ip_pack(data, source->ip); + memcpy(data + SIZE_IP, &source->port, SIZE_PORT); +} + +/* return 0 on success, -1 on failure. */ +static int ipport_unpack(IP_Port *target, const uint8_t *data, unsigned int data_size, bool disable_family_check) +{ + if (data_size < (SIZE_IP + SIZE_PORT)) { + return -1; + } + + if (ip_unpack(&target->ip, data, data_size, disable_family_check) == -1) { + return -1; + } + + memcpy(&target->port, data + SIZE_IP, SIZE_PORT); + return 0; +} + + +/* Create a new onion path. + * + * Create a new onion path out of nodes (nodes is a list of ONION_PATH_LENGTH nodes) + * + * new_path must be an empty memory location of atleast Onion_Path size. + * + * return -1 on failure. + * return 0 on success. + */ +int create_onion_path(const DHT *dht, Onion_Path *new_path, const Node_format *nodes) +{ + if (!new_path || !nodes) { + return -1; + } + + encrypt_precompute(nodes[0].public_key, dht->self_secret_key, new_path->shared_key1); + memcpy(new_path->public_key1, dht->self_public_key, CRYPTO_PUBLIC_KEY_SIZE); + + uint8_t random_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t random_secret_key[CRYPTO_SECRET_KEY_SIZE]; + + crypto_new_keypair(random_public_key, random_secret_key); + encrypt_precompute(nodes[1].public_key, random_secret_key, new_path->shared_key2); + memcpy(new_path->public_key2, random_public_key, CRYPTO_PUBLIC_KEY_SIZE); + + crypto_new_keypair(random_public_key, random_secret_key); + encrypt_precompute(nodes[2].public_key, random_secret_key, new_path->shared_key3); + memcpy(new_path->public_key3, random_public_key, CRYPTO_PUBLIC_KEY_SIZE); + + new_path->ip_port1 = nodes[0].ip_port; + new_path->ip_port2 = nodes[1].ip_port; + new_path->ip_port3 = nodes[2].ip_port; + + memcpy(new_path->node_public_key1, nodes[0].public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(new_path->node_public_key2, nodes[1].public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(new_path->node_public_key3, nodes[2].public_key, CRYPTO_PUBLIC_KEY_SIZE); + + return 0; +} + +/* Dump nodes in onion path to nodes of length num_nodes; + * + * return -1 on failure. + * return 0 on success. + */ +int onion_path_to_nodes(Node_format *nodes, unsigned int num_nodes, const Onion_Path *path) +{ + if (num_nodes < ONION_PATH_LENGTH) { + return -1; + } + + nodes[0].ip_port = path->ip_port1; + nodes[1].ip_port = path->ip_port2; + nodes[2].ip_port = path->ip_port3; + + memcpy(nodes[0].public_key, path->node_public_key1, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(nodes[1].public_key, path->node_public_key2, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(nodes[2].public_key, path->node_public_key3, CRYPTO_PUBLIC_KEY_SIZE); + return 0; +} + +/* Create a onion packet. + * + * Use Onion_Path path to create packet for data of length to dest. + * Maximum length of data is ONION_MAX_DATA_SIZE. + * packet should be at least ONION_MAX_PACKET_SIZE big. + * + * return -1 on failure. + * return length of created packet on success. + */ +int create_onion_packet(uint8_t *packet, uint16_t max_packet_length, const Onion_Path *path, IP_Port dest, + const uint8_t *data, uint16_t length) +{ + if (1 + length + SEND_1 > max_packet_length || length == 0) { + return -1; + } + + VLA(uint8_t, step1, SIZE_IPPORT + length); + + ipport_pack(step1, &dest); + memcpy(step1 + SIZE_IPPORT, data, length); + + uint8_t nonce[CRYPTO_NONCE_SIZE]; + random_nonce(nonce); + + VLA(uint8_t, step2, SIZE_IPPORT + SEND_BASE + length); + ipport_pack(step2, &path->ip_port3); + memcpy(step2 + SIZE_IPPORT, path->public_key3, CRYPTO_PUBLIC_KEY_SIZE); + + int len = encrypt_data_symmetric(path->shared_key3, nonce, step1, SIZEOF_VLA(step1), + step2 + SIZE_IPPORT + CRYPTO_PUBLIC_KEY_SIZE); + + if (len != SIZE_IPPORT + length + CRYPTO_MAC_SIZE) { + return -1; + } + + VLA(uint8_t, step3, SIZE_IPPORT + SEND_BASE * 2 + length); + ipport_pack(step3, &path->ip_port2); + memcpy(step3 + SIZE_IPPORT, path->public_key2, CRYPTO_PUBLIC_KEY_SIZE); + len = encrypt_data_symmetric(path->shared_key2, nonce, step2, SIZEOF_VLA(step2), + step3 + SIZE_IPPORT + CRYPTO_PUBLIC_KEY_SIZE); + + if (len != SIZE_IPPORT + SEND_BASE + length + CRYPTO_MAC_SIZE) { + return -1; + } + + packet[0] = NET_PACKET_ONION_SEND_INITIAL; + memcpy(packet + 1, nonce, CRYPTO_NONCE_SIZE); + memcpy(packet + 1 + CRYPTO_NONCE_SIZE, path->public_key1, CRYPTO_PUBLIC_KEY_SIZE); + + len = encrypt_data_symmetric(path->shared_key1, nonce, step3, SIZEOF_VLA(step3), + packet + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE); + + if (len != SIZE_IPPORT + SEND_BASE * 2 + length + CRYPTO_MAC_SIZE) { + return -1; + } + + return 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + len; +} + +/* Create a onion packet to be sent over tcp. + * + * Use Onion_Path path to create packet for data of length to dest. + * Maximum length of data is ONION_MAX_DATA_SIZE. + * packet should be at least ONION_MAX_PACKET_SIZE big. + * + * return -1 on failure. + * return length of created packet on success. + */ +int create_onion_packet_tcp(uint8_t *packet, uint16_t max_packet_length, const Onion_Path *path, IP_Port dest, + const uint8_t *data, uint16_t length) +{ + if (CRYPTO_NONCE_SIZE + SIZE_IPPORT + SEND_BASE * 2 + length > max_packet_length || length == 0) { + return -1; + } + + VLA(uint8_t, step1, SIZE_IPPORT + length); + + ipport_pack(step1, &dest); + memcpy(step1 + SIZE_IPPORT, data, length); + + uint8_t nonce[CRYPTO_NONCE_SIZE]; + random_nonce(nonce); + + VLA(uint8_t, step2, SIZE_IPPORT + SEND_BASE + length); + ipport_pack(step2, &path->ip_port3); + memcpy(step2 + SIZE_IPPORT, path->public_key3, CRYPTO_PUBLIC_KEY_SIZE); + + int len = encrypt_data_symmetric(path->shared_key3, nonce, step1, SIZEOF_VLA(step1), + step2 + SIZE_IPPORT + CRYPTO_PUBLIC_KEY_SIZE); + + if (len != SIZE_IPPORT + length + CRYPTO_MAC_SIZE) { + return -1; + } + + ipport_pack(packet + CRYPTO_NONCE_SIZE, &path->ip_port2); + memcpy(packet + CRYPTO_NONCE_SIZE + SIZE_IPPORT, path->public_key2, CRYPTO_PUBLIC_KEY_SIZE); + len = encrypt_data_symmetric(path->shared_key2, nonce, step2, SIZEOF_VLA(step2), + packet + CRYPTO_NONCE_SIZE + SIZE_IPPORT + CRYPTO_PUBLIC_KEY_SIZE); + + if (len != SIZE_IPPORT + SEND_BASE + length + CRYPTO_MAC_SIZE) { + return -1; + } + + memcpy(packet, nonce, CRYPTO_NONCE_SIZE); + + return CRYPTO_NONCE_SIZE + SIZE_IPPORT + CRYPTO_PUBLIC_KEY_SIZE + len; +} + +/* Create and send a onion packet. + * + * Use Onion_Path path to send data of length to dest. + * Maximum length of data is ONION_MAX_DATA_SIZE. + * + * return -1 on failure. + * return 0 on success. + */ +int send_onion_packet(Networking_Core *net, const Onion_Path *path, IP_Port dest, const uint8_t *data, uint16_t length) +{ + uint8_t packet[ONION_MAX_PACKET_SIZE]; + int len = create_onion_packet(packet, sizeof(packet), path, dest, data, length); + + if (len == -1) { + return -1; + } + + if (sendpacket(net, path->ip_port1, packet, len) != len) { + return -1; + } + + return 0; +} + +/* Create and send a onion response sent initially to dest with. + * Maximum length of data is ONION_RESPONSE_MAX_DATA_SIZE. + * + * return -1 on failure. + * return 0 on success. + */ +int send_onion_response(Networking_Core *net, IP_Port dest, const uint8_t *data, uint16_t length, const uint8_t *ret) +{ + if (length > ONION_RESPONSE_MAX_DATA_SIZE || length == 0) { + return -1; + } + + VLA(uint8_t, packet, 1 + RETURN_3 + length); + packet[0] = NET_PACKET_ONION_RECV_3; + memcpy(packet + 1, ret, RETURN_3); + memcpy(packet + 1 + RETURN_3, data, length); + + if ((uint32_t)sendpacket(net, dest, packet, SIZEOF_VLA(packet)) != SIZEOF_VLA(packet)) { + return -1; + } + + return 0; +} + +static int handle_send_initial(void *object, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) +{ + Onion *onion = (Onion *)object; + + if (length > ONION_MAX_PACKET_SIZE) { + return 1; + } + + if (length <= 1 + SEND_1) { + return 1; + } + + change_symmetric_key(onion); + + uint8_t plain[ONION_MAX_PACKET_SIZE]; + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + get_shared_key(&onion->shared_keys_1, shared_key, onion->dht->self_secret_key, packet + 1 + CRYPTO_NONCE_SIZE); + int len = decrypt_data_symmetric(shared_key, packet + 1, packet + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE, + length - (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE), plain); + + if (len != length - (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_MAC_SIZE)) { + return 1; + } + + return onion_send_1(onion, plain, len, source, packet + 1); +} + +int onion_send_1(const Onion *onion, const uint8_t *plain, uint16_t len, IP_Port source, const uint8_t *nonce) +{ + if (len > ONION_MAX_PACKET_SIZE + SIZE_IPPORT - (1 + CRYPTO_NONCE_SIZE + ONION_RETURN_1)) { + return 1; + } + + if (len <= SIZE_IPPORT + SEND_BASE * 2) { + return 1; + } + + IP_Port send_to; + + if (ipport_unpack(&send_to, plain, len, 0) == -1) { + return 1; + } + + uint8_t ip_port[SIZE_IPPORT]; + ipport_pack(ip_port, &source); + + uint8_t data[ONION_MAX_PACKET_SIZE]; + data[0] = NET_PACKET_ONION_SEND_1; + memcpy(data + 1, nonce, CRYPTO_NONCE_SIZE); + memcpy(data + 1 + CRYPTO_NONCE_SIZE, plain + SIZE_IPPORT, len - SIZE_IPPORT); + uint16_t data_len = 1 + CRYPTO_NONCE_SIZE + (len - SIZE_IPPORT); + uint8_t *ret_part = data + data_len; + random_nonce(ret_part); + len = encrypt_data_symmetric(onion->secret_symmetric_key, ret_part, ip_port, SIZE_IPPORT, + ret_part + CRYPTO_NONCE_SIZE); + + if (len != SIZE_IPPORT + CRYPTO_MAC_SIZE) { + return 1; + } + + data_len += CRYPTO_NONCE_SIZE + len; + + if ((uint32_t)sendpacket(onion->net, send_to, data, data_len) != data_len) { + return 1; + } + + return 0; +} + +static int handle_send_1(void *object, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) +{ + Onion *onion = (Onion *)object; + + if (length > ONION_MAX_PACKET_SIZE) { + return 1; + } + + if (length <= 1 + SEND_2) { + return 1; + } + + change_symmetric_key(onion); + + uint8_t plain[ONION_MAX_PACKET_SIZE]; + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + get_shared_key(&onion->shared_keys_2, shared_key, onion->dht->self_secret_key, packet + 1 + CRYPTO_NONCE_SIZE); + int len = decrypt_data_symmetric(shared_key, packet + 1, packet + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE, + length - (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + RETURN_1), plain); + + if (len != length - (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + RETURN_1 + CRYPTO_MAC_SIZE)) { + return 1; + } + + IP_Port send_to; + + if (ipport_unpack(&send_to, plain, len, 0) == -1) { + return 1; + } + + uint8_t data[ONION_MAX_PACKET_SIZE]; + data[0] = NET_PACKET_ONION_SEND_2; + memcpy(data + 1, packet + 1, CRYPTO_NONCE_SIZE); + memcpy(data + 1 + CRYPTO_NONCE_SIZE, plain + SIZE_IPPORT, len - SIZE_IPPORT); + uint16_t data_len = 1 + CRYPTO_NONCE_SIZE + (len - SIZE_IPPORT); + uint8_t *ret_part = data + data_len; + random_nonce(ret_part); + uint8_t ret_data[RETURN_1 + SIZE_IPPORT]; + ipport_pack(ret_data, &source); + memcpy(ret_data + SIZE_IPPORT, packet + (length - RETURN_1), RETURN_1); + len = encrypt_data_symmetric(onion->secret_symmetric_key, ret_part, ret_data, sizeof(ret_data), + ret_part + CRYPTO_NONCE_SIZE); + + if (len != RETURN_2 - CRYPTO_NONCE_SIZE) { + return 1; + } + + data_len += CRYPTO_NONCE_SIZE + len; + + if ((uint32_t)sendpacket(onion->net, send_to, data, data_len) != data_len) { + return 1; + } + + return 0; +} + +static int handle_send_2(void *object, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) +{ + Onion *onion = (Onion *)object; + + if (length > ONION_MAX_PACKET_SIZE) { + return 1; + } + + if (length <= 1 + SEND_3) { + return 1; + } + + change_symmetric_key(onion); + + uint8_t plain[ONION_MAX_PACKET_SIZE]; + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + get_shared_key(&onion->shared_keys_3, shared_key, onion->dht->self_secret_key, packet + 1 + CRYPTO_NONCE_SIZE); + int len = decrypt_data_symmetric(shared_key, packet + 1, packet + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE, + length - (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + RETURN_2), plain); + + if (len != length - (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + RETURN_2 + CRYPTO_MAC_SIZE)) { + return 1; + } + + IP_Port send_to; + + if (ipport_unpack(&send_to, plain, len, 0) == -1) { + return 1; + } + + uint8_t data[ONION_MAX_PACKET_SIZE]; + memcpy(data, plain + SIZE_IPPORT, len - SIZE_IPPORT); + uint16_t data_len = (len - SIZE_IPPORT); + uint8_t *ret_part = data + (len - SIZE_IPPORT); + random_nonce(ret_part); + uint8_t ret_data[RETURN_2 + SIZE_IPPORT]; + ipport_pack(ret_data, &source); + memcpy(ret_data + SIZE_IPPORT, packet + (length - RETURN_2), RETURN_2); + len = encrypt_data_symmetric(onion->secret_symmetric_key, ret_part, ret_data, sizeof(ret_data), + ret_part + CRYPTO_NONCE_SIZE); + + if (len != RETURN_3 - CRYPTO_NONCE_SIZE) { + return 1; + } + + data_len += RETURN_3; + + if ((uint32_t)sendpacket(onion->net, send_to, data, data_len) != data_len) { + return 1; + } + + return 0; +} + + +static int handle_recv_3(void *object, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) +{ + Onion *onion = (Onion *)object; + + if (length > ONION_MAX_PACKET_SIZE) { + return 1; + } + + if (length <= 1 + RETURN_3) { + return 1; + } + + change_symmetric_key(onion); + + uint8_t plain[SIZE_IPPORT + RETURN_2]; + int len = decrypt_data_symmetric(onion->secret_symmetric_key, packet + 1, packet + 1 + CRYPTO_NONCE_SIZE, + SIZE_IPPORT + RETURN_2 + CRYPTO_MAC_SIZE, plain); + + if ((uint32_t)len != sizeof(plain)) { + return 1; + } + + IP_Port send_to; + + if (ipport_unpack(&send_to, plain, len, 0) == -1) { + return 1; + } + + uint8_t data[ONION_MAX_PACKET_SIZE]; + data[0] = NET_PACKET_ONION_RECV_2; + memcpy(data + 1, plain + SIZE_IPPORT, RETURN_2); + memcpy(data + 1 + RETURN_2, packet + 1 + RETURN_3, length - (1 + RETURN_3)); + uint16_t data_len = 1 + RETURN_2 + (length - (1 + RETURN_3)); + + if ((uint32_t)sendpacket(onion->net, send_to, data, data_len) != data_len) { + return 1; + } + + return 0; +} + +static int handle_recv_2(void *object, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) +{ + Onion *onion = (Onion *)object; + + if (length > ONION_MAX_PACKET_SIZE) { + return 1; + } + + if (length <= 1 + RETURN_2) { + return 1; + } + + change_symmetric_key(onion); + + uint8_t plain[SIZE_IPPORT + RETURN_1]; + int len = decrypt_data_symmetric(onion->secret_symmetric_key, packet + 1, packet + 1 + CRYPTO_NONCE_SIZE, + SIZE_IPPORT + RETURN_1 + CRYPTO_MAC_SIZE, plain); + + if ((uint32_t)len != sizeof(plain)) { + return 1; + } + + IP_Port send_to; + + if (ipport_unpack(&send_to, plain, len, 0) == -1) { + return 1; + } + + uint8_t data[ONION_MAX_PACKET_SIZE]; + data[0] = NET_PACKET_ONION_RECV_1; + memcpy(data + 1, plain + SIZE_IPPORT, RETURN_1); + memcpy(data + 1 + RETURN_1, packet + 1 + RETURN_2, length - (1 + RETURN_2)); + uint16_t data_len = 1 + RETURN_1 + (length - (1 + RETURN_2)); + + if ((uint32_t)sendpacket(onion->net, send_to, data, data_len) != data_len) { + return 1; + } + + return 0; +} + +static int handle_recv_1(void *object, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) +{ + Onion *onion = (Onion *)object; + + if (length > ONION_MAX_PACKET_SIZE) { + return 1; + } + + if (length <= 1 + RETURN_1) { + return 1; + } + + change_symmetric_key(onion); + + uint8_t plain[SIZE_IPPORT]; + int len = decrypt_data_symmetric(onion->secret_symmetric_key, packet + 1, packet + 1 + CRYPTO_NONCE_SIZE, + SIZE_IPPORT + CRYPTO_MAC_SIZE, plain); + + if ((uint32_t)len != SIZE_IPPORT) { + return 1; + } + + IP_Port send_to; + + if (ipport_unpack(&send_to, plain, len, 1) == -1) { + return 1; + } + + uint16_t data_len = length - (1 + RETURN_1); + + if (onion->recv_1_function && + send_to.ip.family != TOX_AF_INET && + send_to.ip.family != TOX_AF_INET6) { + return onion->recv_1_function(onion->callback_object, send_to, packet + (1 + RETURN_1), data_len); + } + + if ((uint32_t)sendpacket(onion->net, send_to, packet + (1 + RETURN_1), data_len) != data_len) { + return 1; + } + + return 0; +} + +void set_callback_handle_recv_1(Onion *onion, int (*function)(void *, IP_Port, const uint8_t *, uint16_t), void *object) +{ + onion->recv_1_function = function; + onion->callback_object = object; +} + +Onion *new_onion(DHT *dht) +{ + if (dht == NULL) { + return NULL; + } + + Onion *onion = (Onion *)calloc(1, sizeof(Onion)); + + if (onion == NULL) { + return NULL; + } + + onion->dht = dht; + onion->net = dht->net; + new_symmetric_key(onion->secret_symmetric_key); + onion->timestamp = unix_time(); + + networking_registerhandler(onion->net, NET_PACKET_ONION_SEND_INITIAL, &handle_send_initial, onion); + networking_registerhandler(onion->net, NET_PACKET_ONION_SEND_1, &handle_send_1, onion); + networking_registerhandler(onion->net, NET_PACKET_ONION_SEND_2, &handle_send_2, onion); + + networking_registerhandler(onion->net, NET_PACKET_ONION_RECV_3, &handle_recv_3, onion); + networking_registerhandler(onion->net, NET_PACKET_ONION_RECV_2, &handle_recv_2, onion); + networking_registerhandler(onion->net, NET_PACKET_ONION_RECV_1, &handle_recv_1, onion); + + return onion; +} + +void kill_onion(Onion *onion) +{ + if (onion == NULL) { + return; + } + + networking_registerhandler(onion->net, NET_PACKET_ONION_SEND_INITIAL, NULL, NULL); + networking_registerhandler(onion->net, NET_PACKET_ONION_SEND_1, NULL, NULL); + networking_registerhandler(onion->net, NET_PACKET_ONION_SEND_2, NULL, NULL); + + networking_registerhandler(onion->net, NET_PACKET_ONION_RECV_3, NULL, NULL); + networking_registerhandler(onion->net, NET_PACKET_ONION_RECV_2, NULL, NULL); + networking_registerhandler(onion->net, NET_PACKET_ONION_RECV_1, NULL, NULL); + + free(onion); +} diff --git a/libs/libtox/src/toxcore/onion.h b/libs/libtox/src/toxcore/onion.h new file mode 100644 index 0000000000..e81b3a52ae --- /dev/null +++ b/libs/libtox/src/toxcore/onion.h @@ -0,0 +1,165 @@ +/* + * Implementation of the onion part of docs/Prevent_Tracking.txt + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2013 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifndef ONION_H +#define ONION_H + +#include "DHT.h" + +typedef struct { + DHT *dht; + Networking_Core *net; + uint8_t secret_symmetric_key[CRYPTO_SYMMETRIC_KEY_SIZE]; + uint64_t timestamp; + + Shared_Keys shared_keys_1; + Shared_Keys shared_keys_2; + Shared_Keys shared_keys_3; + + int (*recv_1_function)(void *, IP_Port, const uint8_t *, uint16_t); + void *callback_object; +} Onion; + +#define ONION_MAX_PACKET_SIZE 1400 + +#define ONION_RETURN_1 (CRYPTO_NONCE_SIZE + SIZE_IPPORT + CRYPTO_MAC_SIZE) +#define ONION_RETURN_2 (CRYPTO_NONCE_SIZE + SIZE_IPPORT + CRYPTO_MAC_SIZE + ONION_RETURN_1) +#define ONION_RETURN_3 (CRYPTO_NONCE_SIZE + SIZE_IPPORT + CRYPTO_MAC_SIZE + ONION_RETURN_2) + +#define ONION_SEND_BASE (CRYPTO_PUBLIC_KEY_SIZE + SIZE_IPPORT + CRYPTO_MAC_SIZE) +#define ONION_SEND_3 (CRYPTO_NONCE_SIZE + ONION_SEND_BASE + ONION_RETURN_2) +#define ONION_SEND_2 (CRYPTO_NONCE_SIZE + ONION_SEND_BASE*2 + ONION_RETURN_1) +#define ONION_SEND_1 (CRYPTO_NONCE_SIZE + ONION_SEND_BASE*3) + +#define ONION_MAX_DATA_SIZE (ONION_MAX_PACKET_SIZE - (ONION_SEND_1 + 1)) +#define ONION_RESPONSE_MAX_DATA_SIZE (ONION_MAX_PACKET_SIZE - (1 + ONION_RETURN_3)) + +#define ONION_PATH_LENGTH 3 + +typedef struct { + uint8_t shared_key1[CRYPTO_SHARED_KEY_SIZE]; + uint8_t shared_key2[CRYPTO_SHARED_KEY_SIZE]; + uint8_t shared_key3[CRYPTO_SHARED_KEY_SIZE]; + + uint8_t public_key1[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t public_key2[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t public_key3[CRYPTO_PUBLIC_KEY_SIZE]; + + IP_Port ip_port1; + uint8_t node_public_key1[CRYPTO_PUBLIC_KEY_SIZE]; + + IP_Port ip_port2; + uint8_t node_public_key2[CRYPTO_PUBLIC_KEY_SIZE]; + + IP_Port ip_port3; + uint8_t node_public_key3[CRYPTO_PUBLIC_KEY_SIZE]; + + uint32_t path_num; +} Onion_Path; + +/* Create a new onion path. + * + * Create a new onion path out of nodes (nodes is a list of ONION_PATH_LENGTH nodes) + * + * new_path must be an empty memory location of atleast Onion_Path size. + * + * return -1 on failure. + * return 0 on success. + */ +int create_onion_path(const DHT *dht, Onion_Path *new_path, const Node_format *nodes); + +/* Dump nodes in onion path to nodes of length num_nodes; + * + * return -1 on failure. + * return 0 on success. + */ +int onion_path_to_nodes(Node_format *nodes, unsigned int num_nodes, const Onion_Path *path); + +/* Create a onion packet. + * + * Use Onion_Path path to create packet for data of length to dest. + * Maximum length of data is ONION_MAX_DATA_SIZE. + * packet should be at least ONION_MAX_PACKET_SIZE big. + * + * return -1 on failure. + * return length of created packet on success. + */ +int create_onion_packet(uint8_t *packet, uint16_t max_packet_length, const Onion_Path *path, IP_Port dest, + const uint8_t *data, uint16_t length); + + +/* Create a onion packet to be sent over tcp. + * + * Use Onion_Path path to create packet for data of length to dest. + * Maximum length of data is ONION_MAX_DATA_SIZE. + * packet should be at least ONION_MAX_PACKET_SIZE big. + * + * return -1 on failure. + * return length of created packet on success. + */ +int create_onion_packet_tcp(uint8_t *packet, uint16_t max_packet_length, const Onion_Path *path, IP_Port dest, + const uint8_t *data, uint16_t length); + +/* Create and send a onion packet. + * + * Use Onion_Path path to send data of length to dest. + * Maximum length of data is ONION_MAX_DATA_SIZE. + * + * return -1 on failure. + * return 0 on success. + */ +int send_onion_packet(Networking_Core *net, const Onion_Path *path, IP_Port dest, const uint8_t *data, uint16_t length); + +/* Create and send a onion response sent initially to dest with. + * Maximum length of data is ONION_RESPONSE_MAX_DATA_SIZE. + * + * return -1 on failure. + * return 0 on success. + */ +int send_onion_response(Networking_Core *net, IP_Port dest, const uint8_t *data, uint16_t length, const uint8_t *ret); + +/* Function to handle/send received decrypted versions of the packet sent with send_onion_packet. + * + * return 0 on success. + * return 1 on failure. + * + * Used to handle these packets that are received in a non traditional way (by TCP for example). + * + * Source family must be set to something else than TOX_AF_INET6 or TOX_AF_INET so that the callback gets called + * when the response is received. + */ +int onion_send_1(const Onion *onion, const uint8_t *plain, uint16_t len, IP_Port source, const uint8_t *nonce); + +/* Set the callback to be called when the dest ip_port doesn't have TOX_AF_INET6 or TOX_AF_INET as the family. + * + * Format: function(void *object, IP_Port dest, uint8_t *data, uint16_t length) + */ +void set_callback_handle_recv_1(Onion *onion, int (*function)(void *, IP_Port, const uint8_t *, uint16_t), + void *object); + +Onion *new_onion(DHT *dht); + +void kill_onion(Onion *onion); + + +#endif diff --git a/libs/libtox/src/toxcore/onion_announce.c b/libs/libtox/src/toxcore/onion_announce.c new file mode 100644 index 0000000000..c093800719 --- /dev/null +++ b/libs/libtox/src/toxcore/onion_announce.c @@ -0,0 +1,497 @@ +/* + * Implementation of the announce part of docs/Prevent_Tracking.txt + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2013 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "onion_announce.h" + +#include "LAN_discovery.h" +#include "util.h" + +#define PING_ID_TIMEOUT ONION_ANNOUNCE_TIMEOUT + +#define ANNOUNCE_REQUEST_SIZE_RECV (ONION_ANNOUNCE_REQUEST_SIZE + ONION_RETURN_3) + +#define DATA_REQUEST_MIN_SIZE ONION_DATA_REQUEST_MIN_SIZE +#define DATA_REQUEST_MIN_SIZE_RECV (DATA_REQUEST_MIN_SIZE + ONION_RETURN_3) + +/* Create an onion announce request packet in packet of max_packet_length (recommended size ONION_ANNOUNCE_REQUEST_SIZE). + * + * dest_client_id is the public key of the node the packet will be sent to. + * public_key and secret_key is the kepair which will be used to encrypt the request. + * ping_id is the ping id that will be sent in the request. + * client_id is the client id of the node we are searching for. + * data_public_key is the public key we want others to encrypt their data packets with. + * sendback_data is the data of ONION_ANNOUNCE_SENDBACK_DATA_LENGTH length that we expect to + * receive back in the response. + * + * return -1 on failure. + * return packet length on success. + */ +int create_announce_request(uint8_t *packet, uint16_t max_packet_length, const uint8_t *dest_client_id, + const uint8_t *public_key, const uint8_t *secret_key, const uint8_t *ping_id, const uint8_t *client_id, + const uint8_t *data_public_key, uint64_t sendback_data) +{ + if (max_packet_length < ONION_ANNOUNCE_REQUEST_SIZE) { + return -1; + } + + uint8_t plain[ONION_PING_ID_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_PUBLIC_KEY_SIZE + + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH]; + memcpy(plain, ping_id, ONION_PING_ID_SIZE); + memcpy(plain + ONION_PING_ID_SIZE, client_id, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(plain + ONION_PING_ID_SIZE + CRYPTO_PUBLIC_KEY_SIZE, data_public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(plain + ONION_PING_ID_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_PUBLIC_KEY_SIZE, &sendback_data, + sizeof(sendback_data)); + + packet[0] = NET_PACKET_ANNOUNCE_REQUEST; + random_nonce(packet + 1); + + int len = encrypt_data(dest_client_id, secret_key, packet + 1, plain, sizeof(plain), + packet + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE); + + if ((uint32_t)len + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE != ONION_ANNOUNCE_REQUEST_SIZE) { + return -1; + } + + memcpy(packet + 1 + CRYPTO_NONCE_SIZE, public_key, CRYPTO_PUBLIC_KEY_SIZE); + + return ONION_ANNOUNCE_REQUEST_SIZE; +} + +/* Create an onion data request packet in packet of max_packet_length (recommended size ONION_MAX_PACKET_SIZE). + * + * public_key is the real public key of the node which we want to send the data of length length to. + * encrypt_public_key is the public key used to encrypt the data packet. + * + * nonce is the nonce to encrypt this packet with + * + * return -1 on failure. + * return 0 on success. + */ +int create_data_request(uint8_t *packet, uint16_t max_packet_length, const uint8_t *public_key, + const uint8_t *encrypt_public_key, const uint8_t *nonce, const uint8_t *data, uint16_t length) +{ + if (DATA_REQUEST_MIN_SIZE + length > max_packet_length) { + return -1; + } + + if (DATA_REQUEST_MIN_SIZE + length > ONION_MAX_DATA_SIZE) { + return -1; + } + + packet[0] = NET_PACKET_ONION_DATA_REQUEST; + memcpy(packet + 1, public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, nonce, CRYPTO_NONCE_SIZE); + + uint8_t random_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t random_secret_key[CRYPTO_SECRET_KEY_SIZE]; + crypto_new_keypair(random_public_key, random_secret_key); + + memcpy(packet + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE, random_public_key, CRYPTO_PUBLIC_KEY_SIZE); + + int len = encrypt_data(encrypt_public_key, random_secret_key, packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, data, length, + packet + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE); + + if (1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + len != DATA_REQUEST_MIN_SIZE + + length) { + return -1; + } + + return DATA_REQUEST_MIN_SIZE + length; +} + +/* Create and send an onion announce request packet. + * + * path is the path the request will take before it is sent to dest. + * + * public_key and secret_key is the kepair which will be used to encrypt the request. + * ping_id is the ping id that will be sent in the request. + * client_id is the client id of the node we are searching for. + * data_public_key is the public key we want others to encrypt their data packets with. + * sendback_data is the data of ONION_ANNOUNCE_SENDBACK_DATA_LENGTH length that we expect to + * receive back in the response. + * + * return -1 on failure. + * return 0 on success. + */ +int send_announce_request(Networking_Core *net, const Onion_Path *path, Node_format dest, const uint8_t *public_key, + const uint8_t *secret_key, const uint8_t *ping_id, const uint8_t *client_id, const uint8_t *data_public_key, + uint64_t sendback_data) +{ + uint8_t request[ONION_ANNOUNCE_REQUEST_SIZE]; + int len = create_announce_request(request, sizeof(request), dest.public_key, public_key, secret_key, ping_id, client_id, + data_public_key, sendback_data); + + if (len != sizeof(request)) { + return -1; + } + + uint8_t packet[ONION_MAX_PACKET_SIZE]; + len = create_onion_packet(packet, sizeof(packet), path, dest.ip_port, request, sizeof(request)); + + if (len == -1) { + return -1; + } + + if (sendpacket(net, path->ip_port1, packet, len) != len) { + return -1; + } + + return 0; +} + +/* Create and send an onion data request packet. + * + * path is the path the request will take before it is sent to dest. + * (if dest knows the person with the public_key they should + * send the packet to that person in the form of a response) + * + * public_key is the real public key of the node which we want to send the data of length length to. + * encrypt_public_key is the public key used to encrypt the data packet. + * + * nonce is the nonce to encrypt this packet with + * + * return -1 on failure. + * return 0 on success. + */ +int send_data_request(Networking_Core *net, const Onion_Path *path, IP_Port dest, const uint8_t *public_key, + const uint8_t *encrypt_public_key, const uint8_t *nonce, const uint8_t *data, uint16_t length) +{ + uint8_t request[ONION_MAX_DATA_SIZE]; + int len = create_data_request(request, sizeof(request), public_key, encrypt_public_key, nonce, data, length); + + if (len == -1) { + return -1; + } + + uint8_t packet[ONION_MAX_PACKET_SIZE]; + len = create_onion_packet(packet, sizeof(packet), path, dest, request, len); + + if (len == -1) { + return -1; + } + + if (sendpacket(net, path->ip_port1, packet, len) != len) { + return -1; + } + + return 0; +} + +/* Generate a ping_id and put it in ping_id */ +static void generate_ping_id(const Onion_Announce *onion_a, uint64_t time, const uint8_t *public_key, + IP_Port ret_ip_port, uint8_t *ping_id) +{ + time /= PING_ID_TIMEOUT; + uint8_t data[CRYPTO_SYMMETRIC_KEY_SIZE + sizeof(time) + CRYPTO_PUBLIC_KEY_SIZE + sizeof(ret_ip_port)]; + memcpy(data, onion_a->secret_bytes, CRYPTO_SYMMETRIC_KEY_SIZE); + memcpy(data + CRYPTO_SYMMETRIC_KEY_SIZE, &time, sizeof(time)); + memcpy(data + CRYPTO_SYMMETRIC_KEY_SIZE + sizeof(time), public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(data + CRYPTO_SYMMETRIC_KEY_SIZE + sizeof(time) + CRYPTO_PUBLIC_KEY_SIZE, &ret_ip_port, sizeof(ret_ip_port)); + crypto_sha256(ping_id, data, sizeof(data)); +} + +/* check if public key is in entries list + * + * return -1 if no + * return position in list if yes + */ +static int in_entries(const Onion_Announce *onion_a, const uint8_t *public_key) +{ + unsigned int i; + + for (i = 0; i < ONION_ANNOUNCE_MAX_ENTRIES; ++i) { + if (!is_timeout(onion_a->entries[i].time, ONION_ANNOUNCE_TIMEOUT) + && public_key_cmp(onion_a->entries[i].public_key, public_key) == 0) { + return i; + } + } + + return -1; +} + +typedef struct { + const uint8_t *base_public_key; + Onion_Announce_Entry entry; +} Cmp_data; + +static int cmp_entry(const void *a, const void *b) +{ + Cmp_data cmp1, cmp2; + memcpy(&cmp1, a, sizeof(Cmp_data)); + memcpy(&cmp2, b, sizeof(Cmp_data)); + Onion_Announce_Entry entry1 = cmp1.entry; + Onion_Announce_Entry entry2 = cmp2.entry; + const uint8_t *cmp_public_key = cmp1.base_public_key; + + int t1 = is_timeout(entry1.time, ONION_ANNOUNCE_TIMEOUT); + int t2 = is_timeout(entry2.time, ONION_ANNOUNCE_TIMEOUT); + + if (t1 && t2) { + return 0; + } + + if (t1) { + return -1; + } + + if (t2) { + return 1; + } + + int close = id_closest(cmp_public_key, entry1.public_key, entry2.public_key); + + if (close == 1) { + return 1; + } + + if (close == 2) { + return -1; + } + + return 0; +} + +static void sort_onion_announce_list(Onion_Announce_Entry *list, unsigned int length, const uint8_t *comp_public_key) +{ + // Pass comp_public_key to qsort with each Client_data entry, so the + // comparison function can use it as the base of comparison. + VLA(Cmp_data, cmp_list, length); + + for (uint32_t i = 0; i < length; i++) { + cmp_list[i].base_public_key = comp_public_key; + cmp_list[i].entry = list[i]; + } + + qsort(cmp_list, length, sizeof(Cmp_data), cmp_entry); + + for (uint32_t i = 0; i < length; i++) { + list[i] = cmp_list[i].entry; + } +} + +/* add entry to entries list + * + * return -1 if failure + * return position if added + */ +static int add_to_entries(Onion_Announce *onion_a, IP_Port ret_ip_port, const uint8_t *public_key, + const uint8_t *data_public_key, const uint8_t *ret) +{ + + int pos = in_entries(onion_a, public_key); + + unsigned int i; + + if (pos == -1) { + for (i = 0; i < ONION_ANNOUNCE_MAX_ENTRIES; ++i) { + if (is_timeout(onion_a->entries[i].time, ONION_ANNOUNCE_TIMEOUT)) { + pos = i; + } + } + } + + if (pos == -1) { + if (id_closest(onion_a->dht->self_public_key, public_key, onion_a->entries[0].public_key) == 1) { + pos = 0; + } + } + + if (pos == -1) { + return -1; + } + + memcpy(onion_a->entries[pos].public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); + onion_a->entries[pos].ret_ip_port = ret_ip_port; + memcpy(onion_a->entries[pos].ret, ret, ONION_RETURN_3); + memcpy(onion_a->entries[pos].data_public_key, data_public_key, CRYPTO_PUBLIC_KEY_SIZE); + onion_a->entries[pos].time = unix_time(); + + sort_onion_announce_list(onion_a->entries, ONION_ANNOUNCE_MAX_ENTRIES, onion_a->dht->self_public_key); + return in_entries(onion_a, public_key); +} + +static int handle_announce_request(void *object, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) +{ + Onion_Announce *onion_a = (Onion_Announce *)object; + + if (length != ANNOUNCE_REQUEST_SIZE_RECV) { + return 1; + } + + const uint8_t *packet_public_key = packet + 1 + CRYPTO_NONCE_SIZE; + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + get_shared_key(&onion_a->shared_keys_recv, shared_key, onion_a->dht->self_secret_key, packet_public_key); + + uint8_t plain[ONION_PING_ID_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_PUBLIC_KEY_SIZE + + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH]; + int len = decrypt_data_symmetric(shared_key, packet + 1, packet + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE, + ONION_PING_ID_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_PUBLIC_KEY_SIZE + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + + CRYPTO_MAC_SIZE, plain); + + if ((uint32_t)len != sizeof(plain)) { + return 1; + } + + uint8_t ping_id1[ONION_PING_ID_SIZE]; + generate_ping_id(onion_a, unix_time(), packet_public_key, source, ping_id1); + + uint8_t ping_id2[ONION_PING_ID_SIZE]; + generate_ping_id(onion_a, unix_time() + PING_ID_TIMEOUT, packet_public_key, source, ping_id2); + + int index = -1; + + uint8_t *data_public_key = plain + ONION_PING_ID_SIZE + CRYPTO_PUBLIC_KEY_SIZE; + + if (crypto_memcmp(ping_id1, plain, ONION_PING_ID_SIZE) == 0 + || crypto_memcmp(ping_id2, plain, ONION_PING_ID_SIZE) == 0) { + index = add_to_entries(onion_a, source, packet_public_key, data_public_key, + packet + (ANNOUNCE_REQUEST_SIZE_RECV - ONION_RETURN_3)); + } else { + index = in_entries(onion_a, plain + ONION_PING_ID_SIZE); + } + + /*Respond with a announce response packet*/ + Node_format nodes_list[MAX_SENT_NODES]; + unsigned int num_nodes = get_close_nodes(onion_a->dht, plain + ONION_PING_ID_SIZE, nodes_list, 0, + LAN_ip(source.ip) == 0, 1); + uint8_t nonce[CRYPTO_NONCE_SIZE]; + random_nonce(nonce); + + uint8_t pl[1 + ONION_PING_ID_SIZE + sizeof(nodes_list)]; + + if (index == -1) { + pl[0] = 0; + memcpy(pl + 1, ping_id2, ONION_PING_ID_SIZE); + } else { + if (public_key_cmp(onion_a->entries[index].public_key, packet_public_key) == 0) { + if (public_key_cmp(onion_a->entries[index].data_public_key, data_public_key) != 0) { + pl[0] = 0; + memcpy(pl + 1, ping_id2, ONION_PING_ID_SIZE); + } else { + pl[0] = 2; + memcpy(pl + 1, ping_id2, ONION_PING_ID_SIZE); + } + } else { + pl[0] = 1; + memcpy(pl + 1, onion_a->entries[index].data_public_key, CRYPTO_PUBLIC_KEY_SIZE); + } + } + + int nodes_length = 0; + + if (num_nodes != 0) { + nodes_length = pack_nodes(pl + 1 + ONION_PING_ID_SIZE, sizeof(nodes_list), nodes_list, num_nodes); + + if (nodes_length <= 0) { + return 1; + } + } + + uint8_t data[ONION_ANNOUNCE_RESPONSE_MAX_SIZE]; + len = encrypt_data_symmetric(shared_key, nonce, pl, 1 + ONION_PING_ID_SIZE + nodes_length, + data + 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + CRYPTO_NONCE_SIZE); + + if (len != 1 + ONION_PING_ID_SIZE + nodes_length + CRYPTO_MAC_SIZE) { + return 1; + } + + data[0] = NET_PACKET_ANNOUNCE_RESPONSE; + memcpy(data + 1, plain + ONION_PING_ID_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_PUBLIC_KEY_SIZE, + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH); + memcpy(data + 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH, nonce, CRYPTO_NONCE_SIZE); + + if (send_onion_response(onion_a->net, source, data, + 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + CRYPTO_NONCE_SIZE + len, + packet + (ANNOUNCE_REQUEST_SIZE_RECV - ONION_RETURN_3)) == -1) { + return 1; + } + + return 0; +} + +static int handle_data_request(void *object, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) +{ + Onion_Announce *onion_a = (Onion_Announce *)object; + + if (length <= DATA_REQUEST_MIN_SIZE_RECV) { + return 1; + } + + if (length > ONION_MAX_PACKET_SIZE) { + return 1; + } + + int index = in_entries(onion_a, packet + 1); + + if (index == -1) { + return 1; + } + + VLA(uint8_t, data, length - (CRYPTO_PUBLIC_KEY_SIZE + ONION_RETURN_3)); + data[0] = NET_PACKET_ONION_DATA_RESPONSE; + memcpy(data + 1, packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, length - (1 + CRYPTO_PUBLIC_KEY_SIZE + ONION_RETURN_3)); + + if (send_onion_response(onion_a->net, onion_a->entries[index].ret_ip_port, data, SIZEOF_VLA(data), + onion_a->entries[index].ret) == -1) { + return 1; + } + + return 0; +} + +Onion_Announce *new_onion_announce(DHT *dht) +{ + if (dht == NULL) { + return NULL; + } + + Onion_Announce *onion_a = (Onion_Announce *)calloc(1, sizeof(Onion_Announce)); + + if (onion_a == NULL) { + return NULL; + } + + onion_a->dht = dht; + onion_a->net = dht->net; + new_symmetric_key(onion_a->secret_bytes); + + networking_registerhandler(onion_a->net, NET_PACKET_ANNOUNCE_REQUEST, &handle_announce_request, onion_a); + networking_registerhandler(onion_a->net, NET_PACKET_ONION_DATA_REQUEST, &handle_data_request, onion_a); + + return onion_a; +} + +void kill_onion_announce(Onion_Announce *onion_a) +{ + if (onion_a == NULL) { + return; + } + + networking_registerhandler(onion_a->net, NET_PACKET_ANNOUNCE_REQUEST, NULL, NULL); + networking_registerhandler(onion_a->net, NET_PACKET_ONION_DATA_REQUEST, NULL, NULL); + free(onion_a); +} diff --git a/libs/libtox/src/toxcore/onion_announce.h b/libs/libtox/src/toxcore/onion_announce.h new file mode 100644 index 0000000000..548d4b798b --- /dev/null +++ b/libs/libtox/src/toxcore/onion_announce.h @@ -0,0 +1,140 @@ +/* + * Implementation of the announce part of docs/Prevent_Tracking.txt + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2013 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifndef ONION_ANNOUNCE_H +#define ONION_ANNOUNCE_H + +#include "onion.h" + +#define ONION_ANNOUNCE_MAX_ENTRIES 160 +#define ONION_ANNOUNCE_TIMEOUT 300 +#define ONION_PING_ID_SIZE CRYPTO_SHA256_SIZE + +#define ONION_ANNOUNCE_SENDBACK_DATA_LENGTH (sizeof(uint64_t)) + +#define ONION_ANNOUNCE_REQUEST_SIZE (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + ONION_PING_ID_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_PUBLIC_KEY_SIZE + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + CRYPTO_MAC_SIZE) + +#define ONION_ANNOUNCE_RESPONSE_MIN_SIZE (1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + CRYPTO_NONCE_SIZE + 1 + ONION_PING_ID_SIZE + CRYPTO_MAC_SIZE) +#define ONION_ANNOUNCE_RESPONSE_MAX_SIZE (ONION_ANNOUNCE_RESPONSE_MIN_SIZE + sizeof(Node_format)*MAX_SENT_NODES) + +#define ONION_DATA_RESPONSE_MIN_SIZE (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_MAC_SIZE) + +#if ONION_PING_ID_SIZE != CRYPTO_PUBLIC_KEY_SIZE +#error announce response packets assume that ONION_PING_ID_SIZE is equal to CRYPTO_PUBLIC_KEY_SIZE +#endif + +#define ONION_DATA_REQUEST_MIN_SIZE (1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_MAC_SIZE) +#define MAX_DATA_REQUEST_SIZE (ONION_MAX_DATA_SIZE - ONION_DATA_REQUEST_MIN_SIZE) + +typedef struct { + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; + IP_Port ret_ip_port; + uint8_t ret[ONION_RETURN_3]; + uint8_t data_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint64_t time; +} Onion_Announce_Entry; + +typedef struct { + DHT *dht; + Networking_Core *net; + Onion_Announce_Entry entries[ONION_ANNOUNCE_MAX_ENTRIES]; + /* This is CRYPTO_SYMMETRIC_KEY_SIZE long just so we can use new_symmetric_key() to fill it */ + uint8_t secret_bytes[CRYPTO_SYMMETRIC_KEY_SIZE]; + + Shared_Keys shared_keys_recv; +} Onion_Announce; + +/* Create an onion announce request packet in packet of max_packet_length (recommended size ONION_ANNOUNCE_REQUEST_SIZE). + * + * dest_client_id is the public key of the node the packet will be sent to. + * public_key and secret_key is the kepair which will be used to encrypt the request. + * ping_id is the ping id that will be sent in the request. + * client_id is the client id of the node we are searching for. + * data_public_key is the public key we want others to encrypt their data packets with. + * sendback_data is the data of ONION_ANNOUNCE_SENDBACK_DATA_LENGTH length that we expect to + * receive back in the response. + * + * return -1 on failure. + * return packet length on success. + */ +int create_announce_request(uint8_t *packet, uint16_t max_packet_length, const uint8_t *dest_client_id, + const uint8_t *public_key, const uint8_t *secret_key, const uint8_t *ping_id, const uint8_t *client_id, + const uint8_t *data_public_key, uint64_t sendback_data); + +/* Create an onion data request packet in packet of max_packet_length (recommended size ONION_MAX_PACKET_SIZE). + * + * public_key is the real public key of the node which we want to send the data of length length to. + * encrypt_public_key is the public key used to encrypt the data packet. + * + * nonce is the nonce to encrypt this packet with + * + * return -1 on failure. + * return 0 on success. + */ +int create_data_request(uint8_t *packet, uint16_t max_packet_length, const uint8_t *public_key, + const uint8_t *encrypt_public_key, const uint8_t *nonce, const uint8_t *data, uint16_t length); + +/* Create and send an onion announce request packet. + * + * path is the path the request will take before it is sent to dest. + * + * public_key and secret_key is the kepair which will be used to encrypt the request. + * ping_id is the ping id that will be sent in the request. + * client_id is the client id of the node we are searching for. + * data_public_key is the public key we want others to encrypt their data packets with. + * sendback_data is the data of ONION_ANNOUNCE_SENDBACK_DATA_LENGTH length that we expect to + * receive back in the response. + * + * return -1 on failure. + * return 0 on success. + */ +int send_announce_request(Networking_Core *net, const Onion_Path *path, Node_format dest, const uint8_t *public_key, + const uint8_t *secret_key, const uint8_t *ping_id, const uint8_t *client_id, const uint8_t *data_public_key, + uint64_t sendback_data); + +/* Create and send an onion data request packet. + * + * path is the path the request will take before it is sent to dest. + * (if dest knows the person with the public_key they should + * send the packet to that person in the form of a response) + * + * public_key is the real public key of the node which we want to send the data of length length to. + * encrypt_public_key is the public key used to encrypt the data packet. + * + * nonce is the nonce to encrypt this packet with + * + * The maximum length of data is MAX_DATA_REQUEST_SIZE. + * + * return -1 on failure. + * return 0 on success. + */ +int send_data_request(Networking_Core *net, const Onion_Path *path, IP_Port dest, const uint8_t *public_key, + const uint8_t *encrypt_public_key, const uint8_t *nonce, const uint8_t *data, uint16_t length); + + +Onion_Announce *new_onion_announce(DHT *dht); + +void kill_onion_announce(Onion_Announce *onion_a); + + +#endif diff --git a/libs/libtox/src/toxcore/onion_client.c b/libs/libtox/src/toxcore/onion_client.c new file mode 100644 index 0000000000..94e9c91652 --- /dev/null +++ b/libs/libtox/src/toxcore/onion_client.c @@ -0,0 +1,1771 @@ +/* + * Implementation of the client part of docs/Prevent_Tracking.txt (The part that + * uses the onion stuff to connect to the friend) + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2013 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "onion_client.h" + +#include "LAN_discovery.h" +#include "util.h" + +/* defines for the array size and + timeout for onion announce packets. */ +#define ANNOUNCE_ARRAY_SIZE 256 +#define ANNOUNCE_TIMEOUT 10 + +/* Add a node to the path_nodes bootstrap array. + * + * return -1 on failure + * return 0 on success + */ +int onion_add_bs_path_node(Onion_Client *onion_c, IP_Port ip_port, const uint8_t *public_key) +{ + if (ip_port.ip.family != TOX_AF_INET && ip_port.ip.family != TOX_AF_INET6) { + return -1; + } + + unsigned int i; + + for (i = 0; i < MAX_PATH_NODES; ++i) { + if (public_key_cmp(public_key, onion_c->path_nodes_bs[i].public_key) == 0) { + return -1; + } + } + + onion_c->path_nodes_bs[onion_c->path_nodes_index_bs % MAX_PATH_NODES].ip_port = ip_port; + memcpy(onion_c->path_nodes_bs[onion_c->path_nodes_index_bs % MAX_PATH_NODES].public_key, public_key, + CRYPTO_PUBLIC_KEY_SIZE); + + uint16_t last = onion_c->path_nodes_index_bs; + ++onion_c->path_nodes_index_bs; + + if (onion_c->path_nodes_index_bs < last) { + onion_c->path_nodes_index_bs = MAX_PATH_NODES + 1; + } + + return 0; +} + +/* Add a node to the path_nodes array. + * + * return -1 on failure + * return 0 on success + */ +static int onion_add_path_node(Onion_Client *onion_c, IP_Port ip_port, const uint8_t *public_key) +{ + if (ip_port.ip.family != TOX_AF_INET && ip_port.ip.family != TOX_AF_INET6) { + return -1; + } + + unsigned int i; + + for (i = 0; i < MAX_PATH_NODES; ++i) { + if (public_key_cmp(public_key, onion_c->path_nodes[i].public_key) == 0) { + return -1; + } + } + + onion_c->path_nodes[onion_c->path_nodes_index % MAX_PATH_NODES].ip_port = ip_port; + memcpy(onion_c->path_nodes[onion_c->path_nodes_index % MAX_PATH_NODES].public_key, public_key, + CRYPTO_PUBLIC_KEY_SIZE); + + uint16_t last = onion_c->path_nodes_index; + ++onion_c->path_nodes_index; + + if (onion_c->path_nodes_index < last) { + onion_c->path_nodes_index = MAX_PATH_NODES + 1; + } + + return 0; +} + +/* Put up to max_num nodes in nodes. + * + * return the number of nodes. + */ +uint16_t onion_backup_nodes(const Onion_Client *onion_c, Node_format *nodes, uint16_t max_num) +{ + unsigned int i; + + if (!max_num) { + return 0; + } + + unsigned int num_nodes = (onion_c->path_nodes_index < MAX_PATH_NODES) ? onion_c->path_nodes_index : MAX_PATH_NODES; + + if (num_nodes == 0) { + return 0; + } + + if (num_nodes < max_num) { + max_num = num_nodes; + } + + for (i = 0; i < max_num; ++i) { + nodes[i] = onion_c->path_nodes[(onion_c->path_nodes_index - (1 + i)) % num_nodes]; + } + + return max_num; +} + +/* Put up to max_num random nodes in nodes. + * + * return the number of nodes. + */ +static uint16_t random_nodes_path_onion(const Onion_Client *onion_c, Node_format *nodes, uint16_t max_num) +{ + unsigned int i; + + if (!max_num) { + return 0; + } + + unsigned int num_nodes = (onion_c->path_nodes_index < MAX_PATH_NODES) ? onion_c->path_nodes_index : MAX_PATH_NODES; + + //if (DHT_non_lan_connected(onion_c->dht)) { + if (DHT_isconnected(onion_c->dht)) { + if (num_nodes == 0) { + return 0; + } + + for (i = 0; i < max_num; ++i) { + nodes[i] = onion_c->path_nodes[rand() % num_nodes]; + } + } else { + int random_tcp = get_random_tcp_con_number(onion_c->c); + + if (random_tcp == -1) { + return 0; + } + + if (num_nodes >= 2) { + nodes[0].ip_port.ip.family = TCP_FAMILY; + nodes[0].ip_port.ip.ip4.uint32 = random_tcp; + + for (i = 1; i < max_num; ++i) { + nodes[i] = onion_c->path_nodes[rand() % num_nodes]; + } + } else { + unsigned int num_nodes_bs = (onion_c->path_nodes_index_bs < MAX_PATH_NODES) ? onion_c->path_nodes_index_bs : + MAX_PATH_NODES; + + if (num_nodes_bs == 0) { + return 0; + } + + nodes[0].ip_port.ip.family = TCP_FAMILY; + nodes[0].ip_port.ip.ip4.uint32 = random_tcp; + + for (i = 1; i < max_num; ++i) { + nodes[i] = onion_c->path_nodes_bs[rand() % num_nodes_bs]; + } + } + } + + return max_num; +} + +/* + * return -1 if nodes are suitable for creating a new path. + * return path number of already existing similar path if one already exists. + */ +static int is_path_used(const Onion_Client_Paths *onion_paths, const Node_format *nodes) +{ + unsigned int i; + + for (i = 0; i < NUMBER_ONION_PATHS; ++i) { + if (is_timeout(onion_paths->last_path_success[i], ONION_PATH_TIMEOUT)) { + continue; + } + + if (is_timeout(onion_paths->path_creation_time[i], ONION_PATH_MAX_LIFETIME)) { + continue; + } + + // TODO(irungentoo): do we really have to check it with the last node? + if (ipport_equal(&onion_paths->paths[i].ip_port1, &nodes[ONION_PATH_LENGTH - 1].ip_port)) { + return i; + } + } + + return -1; +} + +/* is path timed out */ +static bool path_timed_out(Onion_Client_Paths *onion_paths, uint32_t pathnum) +{ + pathnum = pathnum % NUMBER_ONION_PATHS; + + bool is_new = onion_paths->last_path_success[pathnum] == onion_paths->path_creation_time[pathnum]; + uint64_t timeout = is_new ? ONION_PATH_FIRST_TIMEOUT : ONION_PATH_TIMEOUT; + + return ((onion_paths->last_path_used_times[pathnum] >= ONION_PATH_MAX_NO_RESPONSE_USES + && is_timeout(onion_paths->last_path_used[pathnum], timeout)) + || is_timeout(onion_paths->path_creation_time[pathnum], ONION_PATH_MAX_LIFETIME)); +} + +/* should node be considered to have timed out */ +static bool onion_node_timed_out(const Onion_Node *node) +{ + return (node->timestamp == 0 + || (node->unsuccessful_pings >= ONION_NODE_MAX_PINGS + && is_timeout(node->last_pinged, ONION_NODE_TIMEOUT))); +} + +/* Create a new path or use an old suitable one (if pathnum is valid) + * or a random one from onion_paths. + * + * return -1 on failure + * return 0 on success + * + * TODO(irungentoo): Make this function better, it currently probably is + * vulnerable to some attacks that could deanonimize us. + */ +static int random_path(const Onion_Client *onion_c, Onion_Client_Paths *onion_paths, uint32_t pathnum, Onion_Path *path) +{ + if (pathnum == UINT32_MAX) { + pathnum = rand() % NUMBER_ONION_PATHS; + } else { + pathnum = pathnum % NUMBER_ONION_PATHS; + } + + if (path_timed_out(onion_paths, pathnum)) { + Node_format nodes[ONION_PATH_LENGTH]; + + if (random_nodes_path_onion(onion_c, nodes, ONION_PATH_LENGTH) != ONION_PATH_LENGTH) { + return -1; + } + + int n = is_path_used(onion_paths, nodes); + + if (n == -1) { + if (create_onion_path(onion_c->dht, &onion_paths->paths[pathnum], nodes) == -1) { + return -1; + } + + onion_paths->path_creation_time[pathnum] = unix_time(); + onion_paths->last_path_success[pathnum] = onion_paths->path_creation_time[pathnum]; + onion_paths->last_path_used_times[pathnum] = ONION_PATH_MAX_NO_RESPONSE_USES / 2; + + uint32_t path_num = rand(); + path_num /= NUMBER_ONION_PATHS; + path_num *= NUMBER_ONION_PATHS; + path_num += pathnum; + + onion_paths->paths[pathnum].path_num = path_num; + } else { + pathnum = n; + } + } + + if (onion_paths->last_path_used_times[pathnum] < ONION_PATH_MAX_NO_RESPONSE_USES) { + onion_paths->last_path_used[pathnum] = unix_time(); + } + + ++onion_paths->last_path_used_times[pathnum]; + memcpy(path, &onion_paths->paths[pathnum], sizeof(Onion_Path)); + return 0; +} + +/* Does path with path_num exist. */ +static bool path_exists(Onion_Client_Paths *onion_paths, uint32_t path_num) +{ + if (path_timed_out(onion_paths, path_num)) { + return 0; + } + + return onion_paths->paths[path_num % NUMBER_ONION_PATHS].path_num == path_num; +} + +/* Set path timeouts, return the path number. + * + */ +static uint32_t set_path_timeouts(Onion_Client *onion_c, uint32_t num, uint32_t path_num) +{ + if (num > onion_c->num_friends) { + return -1; + } + + Onion_Client_Paths *onion_paths; + + if (num == 0) { + onion_paths = &onion_c->onion_paths_self; + } else { + onion_paths = &onion_c->onion_paths_friends; + } + + if (onion_paths->paths[path_num % NUMBER_ONION_PATHS].path_num == path_num) { + onion_paths->last_path_success[path_num % NUMBER_ONION_PATHS] = unix_time(); + onion_paths->last_path_used_times[path_num % NUMBER_ONION_PATHS] = 0; + + Node_format nodes[ONION_PATH_LENGTH]; + + if (onion_path_to_nodes(nodes, ONION_PATH_LENGTH, &onion_paths->paths[path_num % NUMBER_ONION_PATHS]) == 0) { + unsigned int i; + + for (i = 0; i < ONION_PATH_LENGTH; ++i) { + onion_add_path_node(onion_c, nodes[i].ip_port, nodes[i].public_key); + } + } + + return path_num; + } + + return ~0; +} + +/* Function to send onion packet via TCP and UDP. + * + * return -1 on failure. + * return 0 on success. + */ +static int send_onion_packet_tcp_udp(const Onion_Client *onion_c, const Onion_Path *path, IP_Port dest, + const uint8_t *data, uint16_t length) +{ + if (path->ip_port1.ip.family == TOX_AF_INET || path->ip_port1.ip.family == TOX_AF_INET6) { + uint8_t packet[ONION_MAX_PACKET_SIZE]; + int len = create_onion_packet(packet, sizeof(packet), path, dest, data, length); + + if (len == -1) { + return -1; + } + + if (sendpacket(onion_c->net, path->ip_port1, packet, len) != len) { + return -1; + } + + return 0; + } + + if (path->ip_port1.ip.family == TCP_FAMILY) { + uint8_t packet[ONION_MAX_PACKET_SIZE]; + int len = create_onion_packet_tcp(packet, sizeof(packet), path, dest, data, length); + + if (len == -1) { + return -1; + } + + return send_tcp_onion_request(onion_c->c, path->ip_port1.ip.ip4.uint32, packet, len); + } + + return -1; +} + +/* Creates a sendback for use in an announce request. + * + * num is 0 if we used our secret public key for the announce + * num is 1 + friendnum if we use a temporary one. + * + * Public key is the key we will be sending it to. + * ip_port is the ip_port of the node we will be sending + * it to. + * + * sendback must be at least ONION_ANNOUNCE_SENDBACK_DATA_LENGTH big + * + * return -1 on failure + * return 0 on success + * + */ +static int new_sendback(Onion_Client *onion_c, uint32_t num, const uint8_t *public_key, IP_Port ip_port, + uint32_t path_num, uint64_t *sendback) +{ + uint8_t data[sizeof(uint32_t) + CRYPTO_PUBLIC_KEY_SIZE + sizeof(IP_Port) + sizeof(uint32_t)]; + memcpy(data, &num, sizeof(uint32_t)); + memcpy(data + sizeof(uint32_t), public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(data + sizeof(uint32_t) + CRYPTO_PUBLIC_KEY_SIZE, &ip_port, sizeof(IP_Port)); + memcpy(data + sizeof(uint32_t) + CRYPTO_PUBLIC_KEY_SIZE + sizeof(IP_Port), &path_num, sizeof(uint32_t)); + *sendback = ping_array_add(&onion_c->announce_ping_array, data, sizeof(data)); + + if (*sendback == 0) { + return -1; + } + + return 0; +} + +/* Checks if the sendback is valid and returns the public key contained in it in ret_pubkey and the + * ip contained in it in ret_ip_port + * + * sendback is the sendback ONION_ANNOUNCE_SENDBACK_DATA_LENGTH big + * ret_pubkey must be at least CRYPTO_PUBLIC_KEY_SIZE big + * ret_ip_port must be at least 1 big + * + * return ~0 on failure + * return num (see new_sendback(...)) on success + */ +static uint32_t check_sendback(Onion_Client *onion_c, const uint8_t *sendback, uint8_t *ret_pubkey, + IP_Port *ret_ip_port, uint32_t *path_num) +{ + uint64_t sback; + memcpy(&sback, sendback, sizeof(uint64_t)); + uint8_t data[sizeof(uint32_t) + CRYPTO_PUBLIC_KEY_SIZE + sizeof(IP_Port) + sizeof(uint32_t)]; + + if (ping_array_check(data, sizeof(data), &onion_c->announce_ping_array, sback) != sizeof(data)) { + return ~0; + } + + memcpy(ret_pubkey, data + sizeof(uint32_t), CRYPTO_PUBLIC_KEY_SIZE); + memcpy(ret_ip_port, data + sizeof(uint32_t) + CRYPTO_PUBLIC_KEY_SIZE, sizeof(IP_Port)); + memcpy(path_num, data + sizeof(uint32_t) + CRYPTO_PUBLIC_KEY_SIZE + sizeof(IP_Port), sizeof(uint32_t)); + + uint32_t num; + memcpy(&num, data, sizeof(uint32_t)); + return num; +} + +static int client_send_announce_request(Onion_Client *onion_c, uint32_t num, IP_Port dest, const uint8_t *dest_pubkey, + const uint8_t *ping_id, uint32_t pathnum) +{ + if (num > onion_c->num_friends) { + return -1; + } + + uint64_t sendback; + Onion_Path path; + + if (num == 0) { + if (random_path(onion_c, &onion_c->onion_paths_self, pathnum, &path) == -1) { + return -1; + } + } else { + if (random_path(onion_c, &onion_c->onion_paths_friends, pathnum, &path) == -1) { + return -1; + } + } + + if (new_sendback(onion_c, num, dest_pubkey, dest, path.path_num, &sendback) == -1) { + return -1; + } + + uint8_t zero_ping_id[ONION_PING_ID_SIZE] = {0}; + + if (ping_id == NULL) { + ping_id = zero_ping_id; + } + + uint8_t request[ONION_ANNOUNCE_REQUEST_SIZE]; + int len; + + if (num == 0) { + len = create_announce_request(request, sizeof(request), dest_pubkey, onion_c->c->self_public_key, + onion_c->c->self_secret_key, ping_id, onion_c->c->self_public_key, onion_c->temp_public_key, sendback); + } else { + len = create_announce_request(request, sizeof(request), dest_pubkey, onion_c->friends_list[num - 1].temp_public_key, + onion_c->friends_list[num - 1].temp_secret_key, ping_id, onion_c->friends_list[num - 1].real_public_key, zero_ping_id, + sendback); + } + + if (len == -1) { + return -1; + } + + return send_onion_packet_tcp_udp(onion_c, &path, dest, request, len); +} + +typedef struct { + const uint8_t *base_public_key; + Onion_Node entry; +} Onion_Client_Cmp_data; + +static int onion_client_cmp_entry(const void *a, const void *b) +{ + Onion_Client_Cmp_data cmp1, cmp2; + memcpy(&cmp1, a, sizeof(Onion_Client_Cmp_data)); + memcpy(&cmp2, b, sizeof(Onion_Client_Cmp_data)); + Onion_Node entry1 = cmp1.entry; + Onion_Node entry2 = cmp2.entry; + const uint8_t *cmp_public_key = cmp1.base_public_key; + + int t1 = onion_node_timed_out(&entry1); + int t2 = onion_node_timed_out(&entry2); + + if (t1 && t2) { + return 0; + } + + if (t1) { + return -1; + } + + if (t2) { + return 1; + } + + int close = id_closest(cmp_public_key, entry1.public_key, entry2.public_key); + + if (close == 1) { + return 1; + } + + if (close == 2) { + return -1; + } + + return 0; +} + +static void sort_onion_node_list(Onion_Node *list, unsigned int length, const uint8_t *comp_public_key) +{ + // Pass comp_public_key to qsort with each Client_data entry, so the + // comparison function can use it as the base of comparison. + VLA(Onion_Client_Cmp_data, cmp_list, length); + + for (uint32_t i = 0; i < length; i++) { + cmp_list[i].base_public_key = comp_public_key; + cmp_list[i].entry = list[i]; + } + + qsort(cmp_list, length, sizeof(Onion_Client_Cmp_data), onion_client_cmp_entry); + + for (uint32_t i = 0; i < length; i++) { + list[i] = cmp_list[i].entry; + } +} + +static int client_add_to_list(Onion_Client *onion_c, uint32_t num, const uint8_t *public_key, IP_Port ip_port, + uint8_t is_stored, const uint8_t *pingid_or_key, uint32_t path_used) +{ + if (num > onion_c->num_friends) { + return -1; + } + + Onion_Node *list_nodes = NULL; + uint8_t *reference_id = NULL; + unsigned int list_length; + + if (num == 0) { + list_nodes = onion_c->clients_announce_list; + reference_id = onion_c->c->self_public_key; + list_length = MAX_ONION_CLIENTS_ANNOUNCE; + + if (is_stored == 1 && public_key_cmp(pingid_or_key, onion_c->temp_public_key) != 0) { + is_stored = 0; + } + } else { + if (is_stored >= 2) { + return -1; + } + + if (is_stored == 1) { + onion_c->friends_list[num - 1].last_reported_announced = unix_time(); + } + + list_nodes = onion_c->friends_list[num - 1].clients_list; + reference_id = onion_c->friends_list[num - 1].real_public_key; + list_length = MAX_ONION_CLIENTS; + } + + sort_onion_node_list(list_nodes, list_length, reference_id); + + int index = -1, stored = 0; + unsigned int i; + + if (onion_node_timed_out(&list_nodes[0]) + || id_closest(reference_id, list_nodes[0].public_key, public_key) == 2) { + index = 0; + } + + for (i = 0; i < list_length; ++i) { + if (public_key_cmp(list_nodes[i].public_key, public_key) == 0) { + index = i; + stored = 1; + break; + } + } + + if (index == -1) { + return 0; + } + + memcpy(list_nodes[index].public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); + list_nodes[index].ip_port = ip_port; + + // TODO(irungentoo): remove this and find a better source of nodes to use for paths. + onion_add_path_node(onion_c, ip_port, public_key); + + if (is_stored == 1) { + memcpy(list_nodes[index].data_public_key, pingid_or_key, CRYPTO_PUBLIC_KEY_SIZE); + } else { + memcpy(list_nodes[index].ping_id, pingid_or_key, ONION_PING_ID_SIZE); + } + + list_nodes[index].is_stored = is_stored; + list_nodes[index].timestamp = unix_time(); + list_nodes[index].unsuccessful_pings = 0; + + if (!stored) { + list_nodes[index].last_pinged = 0; + list_nodes[index].added_time = unix_time(); + } + + list_nodes[index].path_used = path_used; + return 0; +} + +static int good_to_ping(Last_Pinged *last_pinged, uint8_t *last_pinged_index, const uint8_t *public_key) +{ + unsigned int i; + + for (i = 0; i < MAX_STORED_PINGED_NODES; ++i) { + if (!is_timeout(last_pinged[i].timestamp, MIN_NODE_PING_TIME)) { + if (public_key_cmp(last_pinged[i].public_key, public_key) == 0) { + return 0; + } + } + } + + memcpy(last_pinged[*last_pinged_index % MAX_STORED_PINGED_NODES].public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); + last_pinged[*last_pinged_index % MAX_STORED_PINGED_NODES].timestamp = unix_time(); + ++*last_pinged_index; + return 1; +} + +static int client_ping_nodes(Onion_Client *onion_c, uint32_t num, const Node_format *nodes, uint16_t num_nodes, + IP_Port source) +{ + if (num > onion_c->num_friends) { + return -1; + } + + if (num_nodes == 0) { + return 0; + } + + Onion_Node *list_nodes = NULL; + uint8_t *reference_id = NULL; + unsigned int list_length; + + Last_Pinged *last_pinged = NULL; + uint8_t *last_pinged_index = NULL; + + if (num == 0) { + list_nodes = onion_c->clients_announce_list; + reference_id = onion_c->c->self_public_key; + list_length = MAX_ONION_CLIENTS_ANNOUNCE; + last_pinged = onion_c->last_pinged; + last_pinged_index = &onion_c->last_pinged_index; + } else { + list_nodes = onion_c->friends_list[num - 1].clients_list; + reference_id = onion_c->friends_list[num - 1].real_public_key; + list_length = MAX_ONION_CLIENTS; + last_pinged = onion_c->friends_list[num - 1].last_pinged; + last_pinged_index = &onion_c->friends_list[num - 1].last_pinged_index; + } + + unsigned int i, j; + int lan_ips_accepted = (LAN_ip(source.ip) == 0); + + for (i = 0; i < num_nodes; ++i) { + + if (!lan_ips_accepted) { + if (LAN_ip(nodes[i].ip_port.ip) == 0) { + continue; + } + } + + if (onion_node_timed_out(&list_nodes[0]) + || id_closest(reference_id, list_nodes[0].public_key, nodes[i].public_key) == 2 + || onion_node_timed_out(&list_nodes[1]) + || id_closest(reference_id, list_nodes[1].public_key, nodes[i].public_key) == 2) { + /* check if node is already in list. */ + for (j = 0; j < list_length; ++j) { + if (public_key_cmp(list_nodes[j].public_key, nodes[i].public_key) == 0) { + break; + } + } + + if (j == list_length && good_to_ping(last_pinged, last_pinged_index, nodes[i].public_key)) { + client_send_announce_request(onion_c, num, nodes[i].ip_port, nodes[i].public_key, NULL, ~0); + } + } + } + + return 0; +} + +static int handle_announce_response(void *object, IP_Port source, const uint8_t *packet, uint16_t length, + void *userdata) +{ + Onion_Client *onion_c = (Onion_Client *)object; + + if (length < ONION_ANNOUNCE_RESPONSE_MIN_SIZE || length > ONION_ANNOUNCE_RESPONSE_MAX_SIZE) { + return 1; + } + + uint16_t len_nodes = length - ONION_ANNOUNCE_RESPONSE_MIN_SIZE; + + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; + IP_Port ip_port; + uint32_t path_num; + uint32_t num = check_sendback(onion_c, packet + 1, public_key, &ip_port, &path_num); + + if (num > onion_c->num_friends) { + return 1; + } + + VLA(uint8_t, plain, 1 + ONION_PING_ID_SIZE + len_nodes); + int len = -1; + + if (num == 0) { + len = decrypt_data(public_key, onion_c->c->self_secret_key, packet + 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH, + packet + 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + CRYPTO_NONCE_SIZE, + length - (1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + CRYPTO_NONCE_SIZE), plain); + } else { + if (onion_c->friends_list[num - 1].status == 0) { + return 1; + } + + len = decrypt_data(public_key, onion_c->friends_list[num - 1].temp_secret_key, + packet + 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH, + packet + 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + CRYPTO_NONCE_SIZE, + length - (1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + CRYPTO_NONCE_SIZE), plain); + } + + if ((uint32_t)len != SIZEOF_VLA(plain)) { + return 1; + } + + uint32_t path_used = set_path_timeouts(onion_c, num, path_num); + + if (client_add_to_list(onion_c, num, public_key, ip_port, plain[0], plain + 1, path_used) == -1) { + return 1; + } + + if (len_nodes != 0) { + Node_format nodes[MAX_SENT_NODES]; + int num_nodes = unpack_nodes(nodes, MAX_SENT_NODES, 0, plain + 1 + ONION_PING_ID_SIZE, len_nodes, 0); + + if (num_nodes <= 0) { + return 1; + } + + if (client_ping_nodes(onion_c, num, nodes, num_nodes, source) == -1) { + return 1; + } + } + + // TODO(irungentoo): LAN vs non LAN ips?, if we are connected only to LAN, are we offline? + onion_c->last_packet_recv = unix_time(); + return 0; +} + +#define DATA_IN_RESPONSE_MIN_SIZE ONION_DATA_IN_RESPONSE_MIN_SIZE + +static int handle_data_response(void *object, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) +{ + Onion_Client *onion_c = (Onion_Client *)object; + + if (length <= (ONION_DATA_RESPONSE_MIN_SIZE + DATA_IN_RESPONSE_MIN_SIZE)) { + return 1; + } + + if (length > MAX_DATA_REQUEST_SIZE) { + return 1; + } + + VLA(uint8_t, temp_plain, length - ONION_DATA_RESPONSE_MIN_SIZE); + int len = decrypt_data(packet + 1 + CRYPTO_NONCE_SIZE, onion_c->temp_secret_key, packet + 1, + packet + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE, + length - (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE), temp_plain); + + if ((uint32_t)len != SIZEOF_VLA(temp_plain)) { + return 1; + } + + VLA(uint8_t, plain, SIZEOF_VLA(temp_plain) - DATA_IN_RESPONSE_MIN_SIZE); + len = decrypt_data(temp_plain, onion_c->c->self_secret_key, packet + 1, temp_plain + CRYPTO_PUBLIC_KEY_SIZE, + SIZEOF_VLA(temp_plain) - CRYPTO_PUBLIC_KEY_SIZE, plain); + + if ((uint32_t)len != SIZEOF_VLA(plain)) { + return 1; + } + + if (!onion_c->Onion_Data_Handlers[plain[0]].function) { + return 1; + } + + return onion_c->Onion_Data_Handlers[plain[0]].function(onion_c->Onion_Data_Handlers[plain[0]].object, temp_plain, plain, + SIZEOF_VLA(plain), userdata); +} + +#define DHTPK_DATA_MIN_LENGTH (1 + sizeof(uint64_t) + CRYPTO_PUBLIC_KEY_SIZE) +#define DHTPK_DATA_MAX_LENGTH (DHTPK_DATA_MIN_LENGTH + sizeof(Node_format)*MAX_SENT_NODES) +static int handle_dhtpk_announce(void *object, const uint8_t *source_pubkey, const uint8_t *data, uint16_t length, + void *userdata) +{ + Onion_Client *onion_c = (Onion_Client *)object; + + if (length < DHTPK_DATA_MIN_LENGTH) { + return 1; + } + + if (length > DHTPK_DATA_MAX_LENGTH) { + return 1; + } + + int friend_num = onion_friend_num(onion_c, source_pubkey); + + if (friend_num == -1) { + return 1; + } + + uint64_t no_replay; + memcpy(&no_replay, data + 1, sizeof(uint64_t)); + net_to_host((uint8_t *) &no_replay, sizeof(no_replay)); + + if (no_replay <= onion_c->friends_list[friend_num].last_noreplay) { + return 1; + } + + onion_c->friends_list[friend_num].last_noreplay = no_replay; + + if (onion_c->friends_list[friend_num].dht_pk_callback) { + onion_c->friends_list[friend_num].dht_pk_callback(onion_c->friends_list[friend_num].dht_pk_callback_object, + onion_c->friends_list[friend_num].dht_pk_callback_number, data + 1 + sizeof(uint64_t), userdata); + } + + onion_set_friend_DHT_pubkey(onion_c, friend_num, data + 1 + sizeof(uint64_t)); + onion_c->friends_list[friend_num].last_seen = unix_time(); + + uint16_t len_nodes = length - DHTPK_DATA_MIN_LENGTH; + + if (len_nodes != 0) { + Node_format nodes[MAX_SENT_NODES]; + int num_nodes = unpack_nodes(nodes, MAX_SENT_NODES, 0, data + 1 + sizeof(uint64_t) + CRYPTO_PUBLIC_KEY_SIZE, + len_nodes, 1); + + if (num_nodes <= 0) { + return 1; + } + + int i; + + for (i = 0; i < num_nodes; ++i) { + uint8_t family = nodes[i].ip_port.ip.family; + + if (family == TOX_AF_INET || family == TOX_AF_INET6) { + DHT_getnodes(onion_c->dht, &nodes[i].ip_port, nodes[i].public_key, onion_c->friends_list[friend_num].dht_public_key); + } else if (family == TCP_INET || family == TCP_INET6) { + if (onion_c->friends_list[friend_num].tcp_relay_node_callback) { + void *obj = onion_c->friends_list[friend_num].tcp_relay_node_callback_object; + uint32_t number = onion_c->friends_list[friend_num].tcp_relay_node_callback_number; + onion_c->friends_list[friend_num].tcp_relay_node_callback(obj, number, nodes[i].ip_port, nodes[i].public_key); + } + } + } + } + + return 0; +} + +static int handle_tcp_onion(void *object, const uint8_t *data, uint16_t length, void *userdata) +{ + if (length == 0) { + return 1; + } + + IP_Port ip_port = {{0}}; + ip_port.ip.family = TCP_FAMILY; + + if (data[0] == NET_PACKET_ANNOUNCE_RESPONSE) { + return handle_announce_response(object, ip_port, data, length, userdata); + } + + if (data[0] == NET_PACKET_ONION_DATA_RESPONSE) { + return handle_data_response(object, ip_port, data, length, userdata); + } + + return 1; +} + +/* Send data of length length to friendnum. + * This data will be received by the friend using the Onion_Data_Handlers callbacks. + * + * Even if this function succeeds, the friend might not receive any data. + * + * return the number of packets sent on success + * return -1 on failure. + */ +int send_onion_data(Onion_Client *onion_c, int friend_num, const uint8_t *data, uint16_t length) +{ + if ((uint32_t)friend_num >= onion_c->num_friends) { + return -1; + } + + if (length + DATA_IN_RESPONSE_MIN_SIZE > MAX_DATA_REQUEST_SIZE) { + return -1; + } + + if (length == 0) { + return -1; + } + + unsigned int i, good_nodes[MAX_ONION_CLIENTS], num_good = 0, num_nodes = 0; + Onion_Node *list_nodes = onion_c->friends_list[friend_num].clients_list; + + for (i = 0; i < MAX_ONION_CLIENTS; ++i) { + if (onion_node_timed_out(&list_nodes[i])) { + continue; + } + + ++num_nodes; + + if (list_nodes[i].is_stored) { + good_nodes[num_good] = i; + ++num_good; + } + } + + if (num_good < (num_nodes - 1) / 4 + 1) { + return -1; + } + + uint8_t nonce[CRYPTO_NONCE_SIZE]; + random_nonce(nonce); + + VLA(uint8_t, packet, DATA_IN_RESPONSE_MIN_SIZE + length); + memcpy(packet, onion_c->c->self_public_key, CRYPTO_PUBLIC_KEY_SIZE); + int len = encrypt_data(onion_c->friends_list[friend_num].real_public_key, onion_c->c->self_secret_key, nonce, data, + length, packet + CRYPTO_PUBLIC_KEY_SIZE); + + if ((uint32_t)len + CRYPTO_PUBLIC_KEY_SIZE != SIZEOF_VLA(packet)) { + return -1; + } + + unsigned int good = 0; + + for (i = 0; i < num_good; ++i) { + Onion_Path path; + + if (random_path(onion_c, &onion_c->onion_paths_friends, ~0, &path) == -1) { + continue; + } + + uint8_t o_packet[ONION_MAX_PACKET_SIZE]; + len = create_data_request(o_packet, sizeof(o_packet), onion_c->friends_list[friend_num].real_public_key, + list_nodes[good_nodes[i]].data_public_key, nonce, packet, SIZEOF_VLA(packet)); + + if (len == -1) { + continue; + } + + if (send_onion_packet_tcp_udp(onion_c, &path, list_nodes[good_nodes[i]].ip_port, o_packet, len) == 0) { + ++good; + } + } + + return good; +} + +/* Try to send the dht public key via the DHT instead of onion + * + * Even if this function succeeds, the friend might not receive any data. + * + * return the number of packets sent on success + * return -1 on failure. + */ +static int send_dht_dhtpk(const Onion_Client *onion_c, int friend_num, const uint8_t *data, uint16_t length) +{ + if ((uint32_t)friend_num >= onion_c->num_friends) { + return -1; + } + + if (!onion_c->friends_list[friend_num].know_dht_public_key) { + return -1; + } + + uint8_t nonce[CRYPTO_NONCE_SIZE]; + random_nonce(nonce); + + VLA(uint8_t, temp, DATA_IN_RESPONSE_MIN_SIZE + CRYPTO_NONCE_SIZE + length); + memcpy(temp, onion_c->c->self_public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(temp + CRYPTO_PUBLIC_KEY_SIZE, nonce, CRYPTO_NONCE_SIZE); + int len = encrypt_data(onion_c->friends_list[friend_num].real_public_key, onion_c->c->self_secret_key, nonce, data, + length, temp + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE); + + if ((uint32_t)len + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE != SIZEOF_VLA(temp)) { + return -1; + } + + uint8_t packet[MAX_CRYPTO_REQUEST_SIZE]; + len = create_request(onion_c->dht->self_public_key, onion_c->dht->self_secret_key, packet, + onion_c->friends_list[friend_num].dht_public_key, temp, SIZEOF_VLA(temp), CRYPTO_PACKET_DHTPK); + + if (len == -1) { + return -1; + } + + return route_tofriend(onion_c->dht, onion_c->friends_list[friend_num].dht_public_key, packet, len); +} + +static int handle_dht_dhtpk(void *object, IP_Port source, const uint8_t *source_pubkey, const uint8_t *packet, + uint16_t length, void *userdata) +{ + Onion_Client *onion_c = (Onion_Client *)object; + + if (length < DHTPK_DATA_MIN_LENGTH + DATA_IN_RESPONSE_MIN_SIZE + CRYPTO_NONCE_SIZE) { + return 1; + } + + if (length > DHTPK_DATA_MAX_LENGTH + DATA_IN_RESPONSE_MIN_SIZE + CRYPTO_NONCE_SIZE) { + return 1; + } + + uint8_t plain[DHTPK_DATA_MAX_LENGTH]; + int len = decrypt_data(packet, onion_c->c->self_secret_key, packet + CRYPTO_PUBLIC_KEY_SIZE, + packet + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE, + length - (CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE), plain); + + if (len != length - (DATA_IN_RESPONSE_MIN_SIZE + CRYPTO_NONCE_SIZE)) { + return 1; + } + + if (public_key_cmp(source_pubkey, plain + 1 + sizeof(uint64_t)) != 0) { + return 1; + } + + return handle_dhtpk_announce(onion_c, packet, plain, len, userdata); +} +/* Send the packets to tell our friends what our DHT public key is. + * + * if onion_dht_both is 0, use only the onion to send the packet. + * if it is 1, use only the dht. + * if it is something else, use both. + * + * return the number of packets sent on success + * return -1 on failure. + */ +static int send_dhtpk_announce(Onion_Client *onion_c, uint16_t friend_num, uint8_t onion_dht_both) +{ + if (friend_num >= onion_c->num_friends) { + return -1; + } + + uint8_t data[DHTPK_DATA_MAX_LENGTH]; + data[0] = ONION_DATA_DHTPK; + uint64_t no_replay = unix_time(); + host_to_net((uint8_t *)&no_replay, sizeof(no_replay)); + memcpy(data + 1, &no_replay, sizeof(no_replay)); + memcpy(data + 1 + sizeof(uint64_t), onion_c->dht->self_public_key, CRYPTO_PUBLIC_KEY_SIZE); + Node_format nodes[MAX_SENT_NODES]; + uint16_t num_relays = copy_connected_tcp_relays(onion_c->c, nodes, (MAX_SENT_NODES / 2)); + uint16_t num_nodes = closelist_nodes(onion_c->dht, &nodes[num_relays], MAX_SENT_NODES - num_relays); + num_nodes += num_relays; + int nodes_len = 0; + + if (num_nodes != 0) { + nodes_len = pack_nodes(data + DHTPK_DATA_MIN_LENGTH, DHTPK_DATA_MAX_LENGTH - DHTPK_DATA_MIN_LENGTH, nodes, + num_nodes); + + if (nodes_len <= 0) { + return -1; + } + } + + int num1 = -1, num2 = -1; + + if (onion_dht_both != 1) { + num1 = send_onion_data(onion_c, friend_num, data, DHTPK_DATA_MIN_LENGTH + nodes_len); + } + + if (onion_dht_both != 0) { + num2 = send_dht_dhtpk(onion_c, friend_num, data, DHTPK_DATA_MIN_LENGTH + nodes_len); + } + + if (num1 == -1) { + return num2; + } + + if (num2 == -1) { + return num1; + } + + return num1 + num2; +} + +/* Get the friend_num of a friend. + * + * return -1 on failure. + * return friend number on success. + */ +int onion_friend_num(const Onion_Client *onion_c, const uint8_t *public_key) +{ + unsigned int i; + + for (i = 0; i < onion_c->num_friends; ++i) { + if (onion_c->friends_list[i].status == 0) { + continue; + } + + if (public_key_cmp(public_key, onion_c->friends_list[i].real_public_key) == 0) { + return i; + } + } + + return -1; +} + +/* Set the size of the friend list to num. + * + * return -1 if realloc fails. + * return 0 if it succeeds. + */ +static int realloc_onion_friends(Onion_Client *onion_c, uint32_t num) +{ + if (num == 0) { + free(onion_c->friends_list); + onion_c->friends_list = NULL; + return 0; + } + + Onion_Friend *newonion_friends = (Onion_Friend *)realloc(onion_c->friends_list, num * sizeof(Onion_Friend)); + + if (newonion_friends == NULL) { + return -1; + } + + onion_c->friends_list = newonion_friends; + return 0; +} + +/* Add a friend who we want to connect to. + * + * return -1 on failure. + * return the friend number on success or if the friend was already added. + */ +int onion_addfriend(Onion_Client *onion_c, const uint8_t *public_key) +{ + int num = onion_friend_num(onion_c, public_key); + + if (num != -1) { + return num; + } + + unsigned int i, index = ~0; + + for (i = 0; i < onion_c->num_friends; ++i) { + if (onion_c->friends_list[i].status == 0) { + index = i; + break; + } + } + + if (index == (uint32_t)~0) { + if (realloc_onion_friends(onion_c, onion_c->num_friends + 1) == -1) { + return -1; + } + + index = onion_c->num_friends; + memset(&(onion_c->friends_list[onion_c->num_friends]), 0, sizeof(Onion_Friend)); + ++onion_c->num_friends; + } + + onion_c->friends_list[index].status = 1; + memcpy(onion_c->friends_list[index].real_public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); + crypto_new_keypair(onion_c->friends_list[index].temp_public_key, onion_c->friends_list[index].temp_secret_key); + return index; +} + +/* Delete a friend. + * + * return -1 on failure. + * return the deleted friend number on success. + */ +int onion_delfriend(Onion_Client *onion_c, int friend_num) +{ + if ((uint32_t)friend_num >= onion_c->num_friends) { + return -1; + } + + //if (onion_c->friends_list[friend_num].know_dht_public_key) + // DHT_delfriend(onion_c->dht, onion_c->friends_list[friend_num].dht_public_key, 0); + + crypto_memzero(&(onion_c->friends_list[friend_num]), sizeof(Onion_Friend)); + unsigned int i; + + for (i = onion_c->num_friends; i != 0; --i) { + if (onion_c->friends_list[i - 1].status != 0) { + break; + } + } + + if (onion_c->num_friends != i) { + onion_c->num_friends = i; + realloc_onion_friends(onion_c, onion_c->num_friends); + } + + return friend_num; +} + +/* Set the function for this friend that will be callbacked with object and number + * when that friends gives us one of the TCP relays he is connected to. + * + * object and number will be passed as argument to this function. + * + * return -1 on failure. + * return 0 on success. + */ +int recv_tcp_relay_handler(Onion_Client *onion_c, int friend_num, int (*tcp_relay_node_callback)(void *object, + uint32_t number, IP_Port ip_port, const uint8_t *public_key), void *object, uint32_t number) +{ + if ((uint32_t)friend_num >= onion_c->num_friends) { + return -1; + } + + onion_c->friends_list[friend_num].tcp_relay_node_callback = tcp_relay_node_callback; + onion_c->friends_list[friend_num].tcp_relay_node_callback_object = object; + onion_c->friends_list[friend_num].tcp_relay_node_callback_number = number; + return 0; +} + +/* Set the function for this friend that will be callbacked with object and number + * when that friend gives us his DHT temporary public key. + * + * object and number will be passed as argument to this function. + * + * return -1 on failure. + * return 0 on success. + */ +int onion_dht_pk_callback(Onion_Client *onion_c, int friend_num, void (*function)(void *data, int32_t number, + const uint8_t *dht_public_key, void *userdata), void *object, uint32_t number) +{ + if ((uint32_t)friend_num >= onion_c->num_friends) { + return -1; + } + + onion_c->friends_list[friend_num].dht_pk_callback = function; + onion_c->friends_list[friend_num].dht_pk_callback_object = object; + onion_c->friends_list[friend_num].dht_pk_callback_number = number; + return 0; +} + +/* Set a friends DHT public key. + * + * return -1 on failure. + * return 0 on success. + */ +int onion_set_friend_DHT_pubkey(Onion_Client *onion_c, int friend_num, const uint8_t *dht_key) +{ + if ((uint32_t)friend_num >= onion_c->num_friends) { + return -1; + } + + if (onion_c->friends_list[friend_num].status == 0) { + return -1; + } + + if (onion_c->friends_list[friend_num].know_dht_public_key) { + if (public_key_cmp(dht_key, onion_c->friends_list[friend_num].dht_public_key) == 0) { + return -1; + } + + onion_c->friends_list[friend_num].know_dht_public_key = 0; + } + + onion_c->friends_list[friend_num].last_seen = unix_time(); + onion_c->friends_list[friend_num].know_dht_public_key = 1; + memcpy(onion_c->friends_list[friend_num].dht_public_key, dht_key, CRYPTO_PUBLIC_KEY_SIZE); + + return 0; +} + +/* Copy friends DHT public key into dht_key. + * + * return 0 on failure (no key copied). + * return 1 on success (key copied). + */ +unsigned int onion_getfriend_DHT_pubkey(const Onion_Client *onion_c, int friend_num, uint8_t *dht_key) +{ + if ((uint32_t)friend_num >= onion_c->num_friends) { + return 0; + } + + if (onion_c->friends_list[friend_num].status == 0) { + return 0; + } + + if (!onion_c->friends_list[friend_num].know_dht_public_key) { + return 0; + } + + memcpy(dht_key, onion_c->friends_list[friend_num].dht_public_key, CRYPTO_PUBLIC_KEY_SIZE); + return 1; +} + +/* Get the ip of friend friendnum and put it in ip_port + * + * return -1, -- if public_key does NOT refer to a friend + * return 0, -- if public_key refers to a friend and we failed to find the friend (yet) + * return 1, ip if public_key refers to a friend and we found him + * + */ +int onion_getfriendip(const Onion_Client *onion_c, int friend_num, IP_Port *ip_port) +{ + uint8_t dht_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + + if (onion_getfriend_DHT_pubkey(onion_c, friend_num, dht_public_key) == 0) { + return -1; + } + + return DHT_getfriendip(onion_c->dht, dht_public_key, ip_port); +} + + +/* Set if friend is online or not. + * NOTE: This function is there and should be used so that we don't send useless packets to the friend if he is online. + * + * is_online 1 means friend is online. + * is_online 0 means friend is offline + * + * return -1 on failure. + * return 0 on success. + */ +int onion_set_friend_online(Onion_Client *onion_c, int friend_num, uint8_t is_online) +{ + if ((uint32_t)friend_num >= onion_c->num_friends) { + return -1; + } + + if (is_online == 0 && onion_c->friends_list[friend_num].is_online == 1) { + onion_c->friends_list[friend_num].last_seen = unix_time(); + } + + onion_c->friends_list[friend_num].is_online = is_online; + + /* This should prevent some clock related issues */ + if (!is_online) { + onion_c->friends_list[friend_num].last_noreplay = 0; + onion_c->friends_list[friend_num].run_count = 0; + } + + return 0; +} + +static void populate_path_nodes(Onion_Client *onion_c) +{ + Node_format nodes_list[MAX_FRIEND_CLIENTS]; + + unsigned int num_nodes = randfriends_nodes(onion_c->dht, nodes_list, MAX_FRIEND_CLIENTS); + + unsigned int i; + + for (i = 0; i < num_nodes; ++i) { + onion_add_path_node(onion_c, nodes_list[i].ip_port, nodes_list[i].public_key); + } +} + +static void populate_path_nodes_tcp(Onion_Client *onion_c) +{ + Node_format nodes_list[MAX_SENT_NODES]; + + unsigned int num_nodes = copy_connected_tcp_relays(onion_c->c, nodes_list, MAX_SENT_NODES);; + unsigned int i; + + for (i = 0; i < num_nodes; ++i) { + onion_add_bs_path_node(onion_c, nodes_list[i].ip_port, nodes_list[i].public_key); + } +} + +#define ANNOUNCE_FRIEND (ONION_NODE_PING_INTERVAL * 6) +#define ANNOUNCE_FRIEND_BEGINNING 3 + +#define RUN_COUNT_FRIEND_ANNOUNCE_BEGINNING 17 + +#define ONION_FRIEND_BACKOFF_FACTOR 4 +#define ONION_FRIEND_MAX_PING_INTERVAL (5*60*MAX_ONION_CLIENTS) + +static void do_friend(Onion_Client *onion_c, uint16_t friendnum) +{ + if (friendnum >= onion_c->num_friends) { + return; + } + + if (onion_c->friends_list[friendnum].status == 0) { + return; + } + + unsigned int interval = ANNOUNCE_FRIEND; + + if (onion_c->friends_list[friendnum].run_count < RUN_COUNT_FRIEND_ANNOUNCE_BEGINNING) { + interval = ANNOUNCE_FRIEND_BEGINNING; + } else { + if (onion_c->friends_list[friendnum].last_reported_announced == 0) { + onion_c->friends_list[friendnum].last_reported_announced = unix_time(); + } + + uint64_t backoff_interval = (unix_time() - onion_c->friends_list[friendnum].last_reported_announced) + / ONION_FRIEND_BACKOFF_FACTOR; + + if (backoff_interval > ONION_FRIEND_MAX_PING_INTERVAL) { + backoff_interval = ONION_FRIEND_MAX_PING_INTERVAL; + } + + if (interval < backoff_interval) { + interval = backoff_interval; + } + } + + unsigned int i, count = 0; + Onion_Node *list_nodes = onion_c->friends_list[friendnum].clients_list; + + if (!onion_c->friends_list[friendnum].is_online) { + // ensure we get a response from some node roughly once per + // (interval / MAX_ONION_CLIENTS) + bool ping_random = true; + + for (i = 0; i < MAX_ONION_CLIENTS; ++i) { + if (!(is_timeout(list_nodes[i].timestamp, interval / MAX_ONION_CLIENTS) + && is_timeout(list_nodes[i].last_pinged, ONION_NODE_PING_INTERVAL))) { + ping_random = false; + break; + } + } + + for (i = 0; i < MAX_ONION_CLIENTS; ++i) { + if (onion_node_timed_out(&list_nodes[i])) { + continue; + } + + ++count; + + + if (list_nodes[i].last_pinged == 0) { + list_nodes[i].last_pinged = unix_time(); + continue; + } + + if (list_nodes[i].unsuccessful_pings >= ONION_NODE_MAX_PINGS) { + continue; + } + + if (is_timeout(list_nodes[i].last_pinged, interval) + || (ping_random && rand() % (MAX_ONION_CLIENTS - i) == 0)) { + if (client_send_announce_request(onion_c, friendnum + 1, list_nodes[i].ip_port, list_nodes[i].public_key, 0, ~0) == 0) { + list_nodes[i].last_pinged = unix_time(); + ++list_nodes[i].unsuccessful_pings; + ping_random = false; + } + } + } + + if (count != MAX_ONION_CLIENTS) { + unsigned int num_nodes = (onion_c->path_nodes_index < MAX_PATH_NODES) ? onion_c->path_nodes_index : MAX_PATH_NODES; + + unsigned int n = num_nodes; + + if (num_nodes > (MAX_ONION_CLIENTS / 2)) { + n = (MAX_ONION_CLIENTS / 2); + } + + if (count <= (uint32_t)rand() % MAX_ONION_CLIENTS) { + if (num_nodes != 0) { + unsigned int j; + + for (j = 0; j < n; ++j) { + unsigned int num = rand() % num_nodes; + client_send_announce_request(onion_c, friendnum + 1, onion_c->path_nodes[num].ip_port, + onion_c->path_nodes[num].public_key, 0, ~0); + } + + ++onion_c->friends_list[friendnum].run_count; + } + } + } else { + ++onion_c->friends_list[friendnum].run_count; + } + + /* send packets to friend telling them our DHT public key. */ + if (is_timeout(onion_c->friends_list[friendnum].last_dht_pk_onion_sent, ONION_DHTPK_SEND_INTERVAL)) { + if (send_dhtpk_announce(onion_c, friendnum, 0) >= 1) { + onion_c->friends_list[friendnum].last_dht_pk_onion_sent = unix_time(); + } + } + + if (is_timeout(onion_c->friends_list[friendnum].last_dht_pk_dht_sent, DHT_DHTPK_SEND_INTERVAL)) { + if (send_dhtpk_announce(onion_c, friendnum, 1) >= 1) { + onion_c->friends_list[friendnum].last_dht_pk_dht_sent = unix_time(); + } + } + } +} + + +/* Function to call when onion data packet with contents beginning with byte is received. */ +void oniondata_registerhandler(Onion_Client *onion_c, uint8_t byte, oniondata_handler_callback cb, void *object) +{ + onion_c->Onion_Data_Handlers[byte].function = cb; + onion_c->Onion_Data_Handlers[byte].object = object; +} + +#define ANNOUNCE_INTERVAL_NOT_ANNOUNCED 3 +#define ANNOUNCE_INTERVAL_ANNOUNCED ONION_NODE_PING_INTERVAL + +#define TIME_TO_STABLE (ONION_NODE_PING_INTERVAL * 6) +#define ANNOUNCE_INTERVAL_STABLE (ONION_NODE_PING_INTERVAL * 8) + +static void do_announce(Onion_Client *onion_c) +{ + unsigned int i, count = 0; + Onion_Node *list_nodes = onion_c->clients_announce_list; + + for (i = 0; i < MAX_ONION_CLIENTS_ANNOUNCE; ++i) { + if (onion_node_timed_out(&list_nodes[i])) { + continue; + } + + ++count; + + /* Don't announce ourselves the first time this is run to new peers */ + if (list_nodes[i].last_pinged == 0) { + list_nodes[i].last_pinged = 1; + continue; + } + + if (list_nodes[i].unsuccessful_pings >= ONION_NODE_MAX_PINGS) { + continue; + } + + + unsigned int interval = ANNOUNCE_INTERVAL_NOT_ANNOUNCED; + + if (list_nodes[i].is_stored && path_exists(&onion_c->onion_paths_self, list_nodes[i].path_used)) { + interval = ANNOUNCE_INTERVAL_ANNOUNCED; + + uint32_t pathnum = list_nodes[i].path_used % NUMBER_ONION_PATHS; + + /* A node/path is considered 'stable', and can be pinged less + * aggressively, if it has survived for at least TIME_TO_STABLE + * and the latest packets sent to it are not timing out. + */ + if (is_timeout(list_nodes[i].added_time, TIME_TO_STABLE) + && !(list_nodes[i].unsuccessful_pings > 0 + && is_timeout(list_nodes[i].last_pinged, ONION_NODE_TIMEOUT)) + && is_timeout(onion_c->onion_paths_self.path_creation_time[pathnum], TIME_TO_STABLE) + && !(onion_c->onion_paths_self.last_path_used_times[pathnum] > 0 + && is_timeout(onion_c->onion_paths_self.last_path_used[pathnum], ONION_PATH_TIMEOUT))) { + interval = ANNOUNCE_INTERVAL_STABLE; + } + } + + if (is_timeout(list_nodes[i].last_pinged, interval) + || (is_timeout(onion_c->last_announce, ONION_NODE_PING_INTERVAL) + && rand() % (MAX_ONION_CLIENTS_ANNOUNCE - i) == 0)) { + uint32_t path_to_use = list_nodes[i].path_used; + + if (list_nodes[i].unsuccessful_pings == ONION_NODE_MAX_PINGS - 1 + && is_timeout(list_nodes[i].added_time, TIME_TO_STABLE)) { + /* Last chance for a long-lived node - try a random path */ + path_to_use = ~0; + } + + if (client_send_announce_request(onion_c, 0, list_nodes[i].ip_port, list_nodes[i].public_key, + list_nodes[i].ping_id, path_to_use) == 0) { + list_nodes[i].last_pinged = unix_time(); + ++list_nodes[i].unsuccessful_pings; + onion_c->last_announce = unix_time(); + } + } + } + + if (count != MAX_ONION_CLIENTS_ANNOUNCE) { + unsigned int num_nodes; + Node_format *path_nodes; + + if (rand() % 2 == 0 || onion_c->path_nodes_index == 0) { + num_nodes = (onion_c->path_nodes_index_bs < MAX_PATH_NODES) ? onion_c->path_nodes_index_bs : MAX_PATH_NODES; + path_nodes = onion_c->path_nodes_bs; + } else { + num_nodes = (onion_c->path_nodes_index < MAX_PATH_NODES) ? onion_c->path_nodes_index : MAX_PATH_NODES; + path_nodes = onion_c->path_nodes; + } + + if (count <= (uint32_t)rand() % MAX_ONION_CLIENTS_ANNOUNCE) { + if (num_nodes != 0) { + for (i = 0; i < (MAX_ONION_CLIENTS_ANNOUNCE / 2); ++i) { + unsigned int num = rand() % num_nodes; + client_send_announce_request(onion_c, 0, path_nodes[num].ip_port, path_nodes[num].public_key, 0, ~0); + } + } + } + } +} + +/* return 0 if we are not connected to the network. + * return 1 if we are. + */ +static int onion_isconnected(const Onion_Client *onion_c) +{ + unsigned int i, num = 0, announced = 0; + + if (is_timeout(onion_c->last_packet_recv, ONION_OFFLINE_TIMEOUT)) { + return 0; + } + + if (onion_c->path_nodes_index == 0) { + return 0; + } + + for (i = 0; i < MAX_ONION_CLIENTS_ANNOUNCE; ++i) { + if (!onion_node_timed_out(&onion_c->clients_announce_list[i])) { + ++num; + + if (onion_c->clients_announce_list[i].is_stored) { + ++announced; + } + } + } + + unsigned int pnodes = onion_c->path_nodes_index; + + if (pnodes > MAX_ONION_CLIENTS_ANNOUNCE) { + pnodes = MAX_ONION_CLIENTS_ANNOUNCE; + } + + /* Consider ourselves online if we are announced to half or more nodes + we are connected to */ + if (num && announced) { + if ((num / 2) <= announced && (pnodes / 2) <= num) { + return 1; + } + } + + return 0; +} + +#define ONION_CONNECTION_SECONDS 3 + +/* return 0 if we are not connected to the network. + * return 1 if we are connected with TCP only. + * return 2 if we are also connected with UDP. + */ +unsigned int onion_connection_status(const Onion_Client *onion_c) +{ + if (onion_c->onion_connected >= ONION_CONNECTION_SECONDS) { + if (onion_c->UDP_connected) { + return 2; + } + + return 1; + } + + return 0; +} + +void do_onion_client(Onion_Client *onion_c) +{ + unsigned int i; + + if (onion_c->last_run == unix_time()) { + return; + } + + if (is_timeout(onion_c->first_run, ONION_CONNECTION_SECONDS)) { + populate_path_nodes(onion_c); + do_announce(onion_c); + } + + if (onion_isconnected(onion_c)) { + if (onion_c->onion_connected < ONION_CONNECTION_SECONDS * 2) { + ++onion_c->onion_connected; + } + } else { + populate_path_nodes_tcp(onion_c); + + if (onion_c->onion_connected != 0) { + --onion_c->onion_connected; + } + } + + bool UDP_connected = DHT_non_lan_connected(onion_c->dht); + + if (is_timeout(onion_c->first_run, ONION_CONNECTION_SECONDS * 2)) { + set_tcp_onion_status(onion_c->c->tcp_c, !UDP_connected); + } + + onion_c->UDP_connected = UDP_connected + || get_random_tcp_onion_conn_number(onion_c->c->tcp_c) == -1; /* Check if connected to any TCP relays. */ + + if (onion_connection_status(onion_c)) { + for (i = 0; i < onion_c->num_friends; ++i) { + do_friend(onion_c, i); + } + } + + if (onion_c->last_run == 0) { + onion_c->first_run = unix_time(); + } + + onion_c->last_run = unix_time(); +} + +Onion_Client *new_onion_client(Net_Crypto *c) +{ + if (c == NULL) { + return NULL; + } + + Onion_Client *onion_c = (Onion_Client *)calloc(1, sizeof(Onion_Client)); + + if (onion_c == NULL) { + return NULL; + } + + if (ping_array_init(&onion_c->announce_ping_array, ANNOUNCE_ARRAY_SIZE, ANNOUNCE_TIMEOUT) != 0) { + free(onion_c); + return NULL; + } + + onion_c->dht = c->dht; + onion_c->net = c->dht->net; + onion_c->c = c; + new_symmetric_key(onion_c->secret_symmetric_key); + crypto_new_keypair(onion_c->temp_public_key, onion_c->temp_secret_key); + networking_registerhandler(onion_c->net, NET_PACKET_ANNOUNCE_RESPONSE, &handle_announce_response, onion_c); + networking_registerhandler(onion_c->net, NET_PACKET_ONION_DATA_RESPONSE, &handle_data_response, onion_c); + oniondata_registerhandler(onion_c, ONION_DATA_DHTPK, &handle_dhtpk_announce, onion_c); + cryptopacket_registerhandler(onion_c->dht, CRYPTO_PACKET_DHTPK, &handle_dht_dhtpk, onion_c); + set_onion_packet_tcp_connection_callback(onion_c->c->tcp_c, &handle_tcp_onion, onion_c); + + return onion_c; +} + +void kill_onion_client(Onion_Client *onion_c) +{ + if (onion_c == NULL) { + return; + } + + ping_array_free_all(&onion_c->announce_ping_array); + realloc_onion_friends(onion_c, 0); + networking_registerhandler(onion_c->net, NET_PACKET_ANNOUNCE_RESPONSE, NULL, NULL); + networking_registerhandler(onion_c->net, NET_PACKET_ONION_DATA_RESPONSE, NULL, NULL); + oniondata_registerhandler(onion_c, ONION_DATA_DHTPK, NULL, NULL); + cryptopacket_registerhandler(onion_c->dht, CRYPTO_PACKET_DHTPK, NULL, NULL); + set_onion_packet_tcp_connection_callback(onion_c->c->tcp_c, NULL, NULL); + crypto_memzero(onion_c, sizeof(Onion_Client)); + free(onion_c); +} + diff --git a/libs/libtox/src/toxcore/onion_client.h b/libs/libtox/src/toxcore/onion_client.h new file mode 100644 index 0000000000..216dbec050 --- /dev/null +++ b/libs/libtox/src/toxcore/onion_client.h @@ -0,0 +1,302 @@ +/* + * Implementation of the client part of docs/Prevent_Tracking.txt (The part that + * uses the onion stuff to connect to the friend) + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2013 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifndef ONION_CLIENT_H +#define ONION_CLIENT_H + +#include "net_crypto.h" +#include "onion_announce.h" +#include "ping_array.h" + +#define MAX_ONION_CLIENTS 8 +#define MAX_ONION_CLIENTS_ANNOUNCE 12 /* Number of nodes to announce ourselves to. */ +#define ONION_NODE_PING_INTERVAL 15 +#define ONION_NODE_TIMEOUT ONION_NODE_PING_INTERVAL + +/* The interval in seconds at which to tell our friends where we are */ +#define ONION_DHTPK_SEND_INTERVAL 30 +#define DHT_DHTPK_SEND_INTERVAL 20 + +#define NUMBER_ONION_PATHS 6 + +/* The timeout the first time the path is added and + then for all the next consecutive times */ +#define ONION_PATH_FIRST_TIMEOUT 4 +#define ONION_PATH_TIMEOUT 10 +#define ONION_PATH_MAX_LIFETIME 1200 +#define ONION_PATH_MAX_NO_RESPONSE_USES 4 + +#define MAX_STORED_PINGED_NODES 9 +#define MIN_NODE_PING_TIME 10 + +#define ONION_NODE_MAX_PINGS 3 + +#define MAX_PATH_NODES 32 + +/* If no announce response packets are received within this interval tox will + * be considered offline. We give time for a node to be pinged often enough + * that it times out, which leads to the network being thoroughly tested as it + * is replaced. + */ +#define ONION_OFFLINE_TIMEOUT (ONION_NODE_PING_INTERVAL * (ONION_NODE_MAX_PINGS+2)) + +/* Onion data packet ids. */ +#define ONION_DATA_FRIEND_REQ CRYPTO_PACKET_FRIEND_REQ +#define ONION_DATA_DHTPK CRYPTO_PACKET_DHTPK + +typedef struct { + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; + IP_Port ip_port; + uint8_t ping_id[ONION_PING_ID_SIZE]; + uint8_t data_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t is_stored; + + uint64_t added_time; + + uint64_t timestamp; + + uint64_t last_pinged; + + uint8_t unsuccessful_pings; + + uint32_t path_used; +} Onion_Node; + +typedef struct { + Onion_Path paths[NUMBER_ONION_PATHS]; + uint64_t last_path_success[NUMBER_ONION_PATHS]; + uint64_t last_path_used[NUMBER_ONION_PATHS]; + uint64_t path_creation_time[NUMBER_ONION_PATHS]; + /* number of times used without success. */ + unsigned int last_path_used_times[NUMBER_ONION_PATHS]; +} Onion_Client_Paths; + +typedef struct { + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint64_t timestamp; +} Last_Pinged; + +typedef struct { + uint8_t status; /* 0 if friend is not valid, 1 if friend is valid.*/ + uint8_t is_online; /* Set by the onion_set_friend_status function. */ + + uint8_t know_dht_public_key; /* 0 if we don't know the dht public key of the other, 1 if we do. */ + uint8_t dht_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t real_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + + Onion_Node clients_list[MAX_ONION_CLIENTS]; + uint8_t temp_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t temp_secret_key[CRYPTO_SECRET_KEY_SIZE]; + + uint64_t last_reported_announced; + + uint64_t last_dht_pk_onion_sent; + uint64_t last_dht_pk_dht_sent; + + uint64_t last_noreplay; + + uint64_t last_seen; + + Last_Pinged last_pinged[MAX_STORED_PINGED_NODES]; + uint8_t last_pinged_index; + + int (*tcp_relay_node_callback)(void *object, uint32_t number, IP_Port ip_port, const uint8_t *public_key); + void *tcp_relay_node_callback_object; + uint32_t tcp_relay_node_callback_number; + + void (*dht_pk_callback)(void *data, int32_t number, const uint8_t *dht_public_key, void *userdata); + void *dht_pk_callback_object; + uint32_t dht_pk_callback_number; + + uint32_t run_count; +} Onion_Friend; + +typedef int (*oniondata_handler_callback)(void *object, const uint8_t *source_pubkey, const uint8_t *data, + uint16_t len, void *userdata); + +typedef struct { + DHT *dht; + Net_Crypto *c; + Networking_Core *net; + Onion_Friend *friends_list; + uint16_t num_friends; + + Onion_Node clients_announce_list[MAX_ONION_CLIENTS_ANNOUNCE]; + uint64_t last_announce; + + Onion_Client_Paths onion_paths_self; + Onion_Client_Paths onion_paths_friends; + + uint8_t secret_symmetric_key[CRYPTO_SYMMETRIC_KEY_SIZE]; + uint64_t last_run, first_run; + + uint8_t temp_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t temp_secret_key[CRYPTO_SECRET_KEY_SIZE]; + + Last_Pinged last_pinged[MAX_STORED_PINGED_NODES]; + + Node_format path_nodes[MAX_PATH_NODES]; + uint16_t path_nodes_index; + + Node_format path_nodes_bs[MAX_PATH_NODES]; + uint16_t path_nodes_index_bs; + + Ping_Array announce_ping_array; + uint8_t last_pinged_index; + struct { + oniondata_handler_callback function; + void *object; + } Onion_Data_Handlers[256]; + + uint64_t last_packet_recv; + + unsigned int onion_connected; + bool UDP_connected; +} Onion_Client; + + +/* Add a node to the path_nodes bootstrap array. + * + * return -1 on failure + * return 0 on success + */ +int onion_add_bs_path_node(Onion_Client *onion_c, IP_Port ip_port, const uint8_t *public_key); + +/* Put up to max_num nodes in nodes. + * + * return the number of nodes. + */ +uint16_t onion_backup_nodes(const Onion_Client *onion_c, Node_format *nodes, uint16_t max_num); + +/* Add a friend who we want to connect to. + * + * return -1 on failure. + * return the friend number on success or if the friend was already added. + */ +int onion_friend_num(const Onion_Client *onion_c, const uint8_t *public_key); + +/* Add a friend who we want to connect to. + * + * return -1 on failure. + * return the friend number on success. + */ +int onion_addfriend(Onion_Client *onion_c, const uint8_t *public_key); + +/* Delete a friend. + * + * return -1 on failure. + * return the deleted friend number on success. + */ +int onion_delfriend(Onion_Client *onion_c, int friend_num); + +/* Set if friend is online or not. + * NOTE: This function is there and should be used so that we don't send useless packets to the friend if he is online. + * + * is_online 1 means friend is online. + * is_online 0 means friend is offline + * + * return -1 on failure. + * return 0 on success. + */ +int onion_set_friend_online(Onion_Client *onion_c, int friend_num, uint8_t is_online); + +/* Get the ip of friend friendnum and put it in ip_port + * + * return -1, -- if public_key does NOT refer to a friend + * return 0, -- if public_key refers to a friend and we failed to find the friend (yet) + * return 1, ip if public_key refers to a friend and we found him + * + */ +int onion_getfriendip(const Onion_Client *onion_c, int friend_num, IP_Port *ip_port); + +/* Set the function for this friend that will be callbacked with object and number + * when that friends gives us one of the TCP relays he is connected to. + * + * object and number will be passed as argument to this function. + * + * return -1 on failure. + * return 0 on success. + */ +int recv_tcp_relay_handler(Onion_Client *onion_c, int friend_num, int (*tcp_relay_node_callback)(void *object, + uint32_t number, IP_Port ip_port, const uint8_t *public_key), void *object, uint32_t number); + + +/* Set the function for this friend that will be callbacked with object and number + * when that friend gives us his DHT temporary public key. + * + * object and number will be passed as argument to this function. + * + * return -1 on failure. + * return 0 on success. + */ +int onion_dht_pk_callback(Onion_Client *onion_c, int friend_num, void (*function)(void *data, int32_t number, + const uint8_t *dht_public_key, void *userdata), void *object, uint32_t number); + +/* Set a friends DHT public key. + * timestamp is the time (current_time_monotonic()) at which the key was last confirmed belonging to + * the other peer. + * + * return -1 on failure. + * return 0 on success. + */ +int onion_set_friend_DHT_pubkey(Onion_Client *onion_c, int friend_num, const uint8_t *dht_key); + +/* Copy friends DHT public key into dht_key. + * + * return 0 on failure (no key copied). + * return 1 on success (key copied). + */ +unsigned int onion_getfriend_DHT_pubkey(const Onion_Client *onion_c, int friend_num, uint8_t *dht_key); + +#define ONION_DATA_IN_RESPONSE_MIN_SIZE (CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_MAC_SIZE) +#define ONION_CLIENT_MAX_DATA_SIZE (MAX_DATA_REQUEST_SIZE - ONION_DATA_IN_RESPONSE_MIN_SIZE) + +/* Send data of length length to friendnum. + * Maximum length of data is ONION_CLIENT_MAX_DATA_SIZE. + * This data will be received by the friend using the Onion_Data_Handlers callbacks. + * + * Even if this function succeeds, the friend might not receive any data. + * + * return the number of packets sent on success + * return -1 on failure. + */ +int send_onion_data(Onion_Client *onion_c, int friend_num, const uint8_t *data, uint16_t length); + +/* Function to call when onion data packet with contents beginning with byte is received. */ +void oniondata_registerhandler(Onion_Client *onion_c, uint8_t byte, oniondata_handler_callback cb, void *object); + +void do_onion_client(Onion_Client *onion_c); + +Onion_Client *new_onion_client(Net_Crypto *c); + +void kill_onion_client(Onion_Client *onion_c); + + +/* return 0 if we are not connected to the network. + * return 1 if we are connected with TCP only. + * return 2 if we are also connected with UDP. + */ +unsigned int onion_connection_status(const Onion_Client *onion_c); + +#endif diff --git a/libs/libtox/src/toxcore/ping.c b/libs/libtox/src/toxcore/ping.c new file mode 100644 index 0000000000..72b3fe6259 --- /dev/null +++ b/libs/libtox/src/toxcore/ping.c @@ -0,0 +1,381 @@ +/* + * Buffered pinging using cyclic arrays. + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2013 Tox project. + * Copyright © 2013 plutooo + * + * This file is part of Tox, the free peer to peer instant messenger. + * This file is donated to the Tox Project. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ping.h" + +#include "DHT.h" +#include "network.h" +#include "ping_array.h" +#include "util.h" + +#include <stdint.h> + +#define PING_NUM_MAX 512 + +/* Maximum newly announced nodes to ping per TIME_TO_PING seconds. */ +#define MAX_TO_PING 32 + +/* Ping newly announced nodes to ping per TIME_TO_PING seconds*/ +#define TIME_TO_PING 2 + + +struct PING { + DHT *dht; + + Ping_Array ping_array; + Node_format to_ping[MAX_TO_PING]; + uint64_t last_to_ping; +}; + + +#define PING_PLAIN_SIZE (1 + sizeof(uint64_t)) +#define DHT_PING_SIZE (1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + PING_PLAIN_SIZE + CRYPTO_MAC_SIZE) +#define PING_DATA_SIZE (CRYPTO_PUBLIC_KEY_SIZE + sizeof(IP_Port)) + +int send_ping_request(PING *ping, IP_Port ipp, const uint8_t *public_key) +{ + uint8_t pk[DHT_PING_SIZE]; + int rc; + uint64_t ping_id; + + if (id_equal(public_key, ping->dht->self_public_key)) { + return 1; + } + + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + + // generate key to encrypt ping_id with recipient privkey + DHT_get_shared_key_sent(ping->dht, shared_key, public_key); + // Generate random ping_id. + uint8_t data[PING_DATA_SIZE]; + id_copy(data, public_key); + memcpy(data + CRYPTO_PUBLIC_KEY_SIZE, &ipp, sizeof(IP_Port)); + ping_id = ping_array_add(&ping->ping_array, data, sizeof(data)); + + if (ping_id == 0) { + return 1; + } + + uint8_t ping_plain[PING_PLAIN_SIZE]; + ping_plain[0] = NET_PACKET_PING_REQUEST; + memcpy(ping_plain + 1, &ping_id, sizeof(ping_id)); + + pk[0] = NET_PACKET_PING_REQUEST; + id_copy(pk + 1, ping->dht->self_public_key); // Our pubkey + random_nonce(pk + 1 + CRYPTO_PUBLIC_KEY_SIZE); // Generate new nonce + + + rc = encrypt_data_symmetric(shared_key, + pk + 1 + CRYPTO_PUBLIC_KEY_SIZE, + ping_plain, sizeof(ping_plain), + pk + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE); + + if (rc != PING_PLAIN_SIZE + CRYPTO_MAC_SIZE) { + return 1; + } + + return sendpacket(ping->dht->net, ipp, pk, sizeof(pk)); +} + +static int send_ping_response(PING *ping, IP_Port ipp, const uint8_t *public_key, uint64_t ping_id, + uint8_t *shared_encryption_key) +{ + uint8_t pk[DHT_PING_SIZE]; + int rc; + + if (id_equal(public_key, ping->dht->self_public_key)) { + return 1; + } + + uint8_t ping_plain[PING_PLAIN_SIZE]; + ping_plain[0] = NET_PACKET_PING_RESPONSE; + memcpy(ping_plain + 1, &ping_id, sizeof(ping_id)); + + pk[0] = NET_PACKET_PING_RESPONSE; + id_copy(pk + 1, ping->dht->self_public_key); // Our pubkey + random_nonce(pk + 1 + CRYPTO_PUBLIC_KEY_SIZE); // Generate new nonce + + // Encrypt ping_id using recipient privkey + rc = encrypt_data_symmetric(shared_encryption_key, + pk + 1 + CRYPTO_PUBLIC_KEY_SIZE, + ping_plain, sizeof(ping_plain), + pk + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE); + + if (rc != PING_PLAIN_SIZE + CRYPTO_MAC_SIZE) { + return 1; + } + + return sendpacket(ping->dht->net, ipp, pk, sizeof(pk)); +} + +static int handle_ping_request(void *object, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) +{ + DHT *dht = (DHT *)object; + int rc; + + if (length != DHT_PING_SIZE) { + return 1; + } + + PING *ping = dht->ping; + + if (id_equal(packet + 1, ping->dht->self_public_key)) { + return 1; + } + + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + + uint8_t ping_plain[PING_PLAIN_SIZE]; + // Decrypt ping_id + DHT_get_shared_key_recv(dht, shared_key, packet + 1); + rc = decrypt_data_symmetric(shared_key, + packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, + packet + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE, + PING_PLAIN_SIZE + CRYPTO_MAC_SIZE, + ping_plain); + + if (rc != sizeof(ping_plain)) { + return 1; + } + + if (ping_plain[0] != NET_PACKET_PING_REQUEST) { + return 1; + } + + uint64_t ping_id; + memcpy(&ping_id, ping_plain + 1, sizeof(ping_id)); + // Send response + send_ping_response(ping, source, packet + 1, ping_id, shared_key); + add_to_ping(ping, packet + 1, source); + + return 0; +} + +static int handle_ping_response(void *object, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) +{ + DHT *dht = (DHT *)object; + int rc; + + if (length != DHT_PING_SIZE) { + return 1; + } + + PING *ping = dht->ping; + + if (id_equal(packet + 1, ping->dht->self_public_key)) { + return 1; + } + + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + + // generate key to encrypt ping_id with recipient privkey + DHT_get_shared_key_sent(ping->dht, shared_key, packet + 1); + + uint8_t ping_plain[PING_PLAIN_SIZE]; + // Decrypt ping_id + rc = decrypt_data_symmetric(shared_key, + packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, + packet + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE, + PING_PLAIN_SIZE + CRYPTO_MAC_SIZE, + ping_plain); + + if (rc != sizeof(ping_plain)) { + return 1; + } + + if (ping_plain[0] != NET_PACKET_PING_RESPONSE) { + return 1; + } + + uint64_t ping_id; + memcpy(&ping_id, ping_plain + 1, sizeof(ping_id)); + uint8_t data[PING_DATA_SIZE]; + + if (ping_array_check(data, sizeof(data), &ping->ping_array, ping_id) != sizeof(data)) { + return 1; + } + + if (!id_equal(packet + 1, data)) { + return 1; + } + + IP_Port ipp; + memcpy(&ipp, data + CRYPTO_PUBLIC_KEY_SIZE, sizeof(IP_Port)); + + if (!ipport_equal(&ipp, &source)) { + return 1; + } + + addto_lists(dht, source, packet + 1); + return 0; +} + +/* Check if public_key with ip_port is in the list. + * + * return 1 if it is. + * return 0 if it isn't. + */ +static int in_list(const Client_data *list, uint16_t length, const uint8_t *public_key, IP_Port ip_port) +{ + unsigned int i; + + for (i = 0; i < length; ++i) { + if (id_equal(list[i].public_key, public_key)) { + const IPPTsPng *ipptp; + + if (ip_port.ip.family == TOX_AF_INET) { + ipptp = &list[i].assoc4; + } else { + ipptp = &list[i].assoc6; + } + + if (!is_timeout(ipptp->timestamp, BAD_NODE_TIMEOUT) && ipport_equal(&ipptp->ip_port, &ip_port)) { + return 1; + } + } + } + + return 0; +} + +/* Add nodes to the to_ping list. + * All nodes in this list are pinged every TIME_TO_PING seconds + * and are then removed from the list. + * If the list is full the nodes farthest from our public_key are replaced. + * The purpose of this list is to enable quick integration of new nodes into the + * network while preventing amplification attacks. + * + * return 0 if node was added. + * return -1 if node was not added. + */ +int add_to_ping(PING *ping, const uint8_t *public_key, IP_Port ip_port) +{ + if (!ip_isset(&ip_port.ip)) { + return -1; + } + + if (!node_addable_to_close_list(ping->dht, public_key, ip_port)) { + return -1; + } + + if (in_list(ping->dht->close_clientlist, LCLIENT_LIST, public_key, ip_port)) { + return -1; + } + + IP_Port temp; + + if (DHT_getfriendip(ping->dht, public_key, &temp) == 0) { + send_ping_request(ping, ip_port, public_key); + return -1; + } + + unsigned int i; + + for (i = 0; i < MAX_TO_PING; ++i) { + if (!ip_isset(&ping->to_ping[i].ip_port.ip)) { + memcpy(ping->to_ping[i].public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); + ipport_copy(&ping->to_ping[i].ip_port, &ip_port); + return 0; + } + + if (public_key_cmp(ping->to_ping[i].public_key, public_key) == 0) { + return -1; + } + } + + if (add_to_list(ping->to_ping, MAX_TO_PING, public_key, ip_port, ping->dht->self_public_key)) { + return 0; + } + + return -1; +} + + +/* Ping all the valid nodes in the to_ping list every TIME_TO_PING seconds. + * This function must be run at least once every TIME_TO_PING seconds. + */ +void do_to_ping(PING *ping) +{ + if (!is_timeout(ping->last_to_ping, TIME_TO_PING)) { + return; + } + + if (!ip_isset(&ping->to_ping[0].ip_port.ip)) { + return; + } + + unsigned int i; + + for (i = 0; i < MAX_TO_PING; ++i) { + if (!ip_isset(&ping->to_ping[i].ip_port.ip)) { + break; + } + + if (!node_addable_to_close_list(ping->dht, ping->to_ping[i].public_key, ping->to_ping[i].ip_port)) { + continue; + } + + send_ping_request(ping, ping->to_ping[i].ip_port, ping->to_ping[i].public_key); + ip_reset(&ping->to_ping[i].ip_port.ip); + } + + if (i != 0) { + ping->last_to_ping = unix_time(); + } +} + + +PING *new_ping(DHT *dht) +{ + PING *ping = (PING *)calloc(1, sizeof(PING)); + + if (ping == NULL) { + return NULL; + } + + if (ping_array_init(&ping->ping_array, PING_NUM_MAX, PING_TIMEOUT) != 0) { + free(ping); + return NULL; + } + + ping->dht = dht; + networking_registerhandler(ping->dht->net, NET_PACKET_PING_REQUEST, &handle_ping_request, dht); + networking_registerhandler(ping->dht->net, NET_PACKET_PING_RESPONSE, &handle_ping_response, dht); + + return ping; +} + +void kill_ping(PING *ping) +{ + networking_registerhandler(ping->dht->net, NET_PACKET_PING_REQUEST, NULL, NULL); + networking_registerhandler(ping->dht->net, NET_PACKET_PING_RESPONSE, NULL, NULL); + ping_array_free_all(&ping->ping_array); + + free(ping); +} diff --git a/libs/libtox/src/toxcore/ping.h b/libs/libtox/src/toxcore/ping.h new file mode 100644 index 0000000000..cc3428c548 --- /dev/null +++ b/libs/libtox/src/toxcore/ping.h @@ -0,0 +1,54 @@ +/* + * Buffered pinging using cyclic arrays. + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2013 Tox project. + * Copyright © 2013 plutooo + * + * This file is part of Tox, the free peer to peer instant messenger. + * This file is donated to the Tox Project. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifndef PING_H +#define PING_H + +#include "DHT.h" +#include "network.h" + +#include <stdint.h> + +typedef struct PING PING; + +/* Add nodes to the to_ping list. + * All nodes in this list are pinged every TIME_TOPING seconds + * and are then removed from the list. + * If the list is full the nodes farthest from our public_key are replaced. + * The purpose of this list is to enable quick integration of new nodes into the + * network while preventing amplification attacks. + * + * return 0 if node was added. + * return -1 if node was not added. + */ +int add_to_ping(PING *ping, const uint8_t *public_key, IP_Port ip_port); +void do_to_ping(PING *ping); + +PING *new_ping(DHT *dht); +void kill_ping(PING *ping); + +int send_ping_request(PING *ping, IP_Port ipp, const uint8_t *public_key); + +#endif /* PING_H */ diff --git a/libs/libtox/src/toxcore/ping_array.c b/libs/libtox/src/toxcore/ping_array.c new file mode 100644 index 0000000000..ea3e5101b1 --- /dev/null +++ b/libs/libtox/src/toxcore/ping_array.c @@ -0,0 +1,172 @@ +/* + * Implementation of an efficient array to store that we pinged something. + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2014 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ping_array.h" + +#include "crypto_core.h" +#include "util.h" + +static void clear_entry(Ping_Array *array, uint32_t index) +{ + free(array->entries[index].data); + array->entries[index].data = NULL; + array->entries[index].length = + array->entries[index].time = + array->entries[index].ping_id = 0; +} + +/* Clear timed out entries. + */ +static void ping_array_clear_timedout(Ping_Array *array) +{ + while (array->last_deleted != array->last_added) { + uint32_t index = array->last_deleted % array->total_size; + + if (!is_timeout(array->entries[index].time, array->timeout)) { + break; + } + + clear_entry(array, index); + ++array->last_deleted; + } +} + +/* Add a data with length to the Ping_Array list and return a ping_id. + * + * return ping_id on success. + * return 0 on failure. + */ +uint64_t ping_array_add(Ping_Array *array, const uint8_t *data, uint32_t length) +{ + ping_array_clear_timedout(array); + uint32_t index = array->last_added % array->total_size; + + if (array->entries[index].data != NULL) { + array->last_deleted = array->last_added - array->total_size; + clear_entry(array, index); + } + + array->entries[index].data = malloc(length); + + if (array->entries[index].data == NULL) { + return 0; + } + + memcpy(array->entries[index].data, data, length); + array->entries[index].length = length; + array->entries[index].time = unix_time(); + ++array->last_added; + uint64_t ping_id = random_64b(); + ping_id /= array->total_size; + ping_id *= array->total_size; + ping_id += index; + + if (ping_id == 0) { + ping_id += array->total_size; + } + + array->entries[index].ping_id = ping_id; + return ping_id; +} + + +/* Check if ping_id is valid and not timed out. + * + * On success, copies the data into data of length, + * + * return length of data copied on success. + * return -1 on failure. + */ +int ping_array_check(uint8_t *data, uint32_t length, Ping_Array *array, uint64_t ping_id) +{ + if (ping_id == 0) { + return -1; + } + + uint32_t index = ping_id % array->total_size; + + if (array->entries[index].ping_id != ping_id) { + return -1; + } + + if (is_timeout(array->entries[index].time, array->timeout)) { + return -1; + } + + if (array->entries[index].length > length) { + return -1; + } + + if (array->entries[index].data == NULL) { + return -1; + } + + memcpy(data, array->entries[index].data, array->entries[index].length); + uint32_t len = array->entries[index].length; + clear_entry(array, index); + return len; +} + +/* Initialize a Ping_Array. + * size represents the total size of the array and should be a power of 2. + * timeout represents the maximum timeout in seconds for the entry. + * + * return 0 on success. + * return -1 on failure. + */ +int ping_array_init(Ping_Array *empty_array, uint32_t size, uint32_t timeout) +{ + if (size == 0 || timeout == 0 || empty_array == NULL) { + return -1; + } + + empty_array->entries = (Ping_Array_Entry *)calloc(size, sizeof(Ping_Array_Entry)); + + if (empty_array->entries == NULL) { + return -1; + } + + empty_array->last_deleted = empty_array->last_added = 0; + empty_array->total_size = size; + empty_array->timeout = timeout; + return 0; +} + +/* Free all the allocated memory in a Ping_Array. + */ +void ping_array_free_all(Ping_Array *array) +{ + while (array->last_deleted != array->last_added) { + uint32_t index = array->last_deleted % array->total_size; + clear_entry(array, index); + ++array->last_deleted; + } + + free(array->entries); + array->entries = NULL; +} + diff --git a/libs/libtox/src/toxcore/ping_array.h b/libs/libtox/src/toxcore/ping_array.h new file mode 100644 index 0000000000..bdf3c6db3d --- /dev/null +++ b/libs/libtox/src/toxcore/ping_array.h @@ -0,0 +1,76 @@ +/* + * Implementation of an efficient array to store that we pinged something. + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2013 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifndef PING_ARRAY_H +#define PING_ARRAY_H + +#include "network.h" + +typedef struct { + void *data; + uint32_t length; + uint64_t time; + uint64_t ping_id; +} Ping_Array_Entry; + + +typedef struct { + Ping_Array_Entry *entries; + + uint32_t last_deleted; /* number representing the next entry to be deleted. */ + uint32_t last_added; /* number representing the last entry to be added. */ + uint32_t total_size; /* The length of entries */ + uint32_t timeout; /* The timeout after which entries are cleared. */ +} Ping_Array; + + +/* Add a data with length to the Ping_Array list and return a ping_id. + * + * return ping_id on success. + * return 0 on failure. + */ +uint64_t ping_array_add(Ping_Array *array, const uint8_t *data, uint32_t length); + +/* Check if ping_id is valid and not timed out. + * + * On success, copies the data into data of length, + * + * return length of data copied on success. + * return -1 on failure. + */ +int ping_array_check(uint8_t *data, uint32_t length, Ping_Array *array, uint64_t ping_id); + +/* Initialize a Ping_Array. + * size represents the total size of the array and should be a power of 2. + * timeout represents the maximum timeout in seconds for the entry. + * + * return 0 on success. + * return -1 on failure. + */ +int ping_array_init(Ping_Array *empty_array, uint32_t size, uint32_t timeout); + +/* Free all the allocated memory in a Ping_Array. + */ +void ping_array_free_all(Ping_Array *array); + +#endif diff --git a/libs/libtox/src/toxcore/tox.api.h b/libs/libtox/src/toxcore/tox.api.h new file mode 100644 index 0000000000..0763c7789d --- /dev/null +++ b/libs/libtox/src/toxcore/tox.api.h @@ -0,0 +1,2583 @@ +%{ +/* + * The Tox public API. + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2013 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifndef TOX_H +#define TOX_H + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif +%} + + +/***************************************************************************** + * `tox.h` SHOULD *NOT* BE EDITED MANUALLY – any changes should be made to * + * `tox.api.h`, located in `toxcore/`. For instructions on how to * + * generate `tox.h` from `tox.api.h` please refer to `docs/apidsl.md` * + *****************************************************************************/ + + +/** \page core Public core API for Tox clients. + * + * Every function that can fail takes a function-specific error code pointer + * that can be used to diagnose problems with the Tox state or the function + * arguments. The error code pointer can be NULL, which does not influence the + * function's behaviour, but can be done if the reason for failure is irrelevant + * to the client. + * + * The exception to this rule are simple allocation functions whose only failure + * mode is allocation failure. They return NULL in that case, and do not set an + * error code. + * + * Every error code type has an OK value to which functions will set their error + * code value on success. Clients can keep their error code uninitialised before + * passing it to a function. The library guarantees that after returning, the + * value pointed to by the error code pointer has been initialised. + * + * Functions with pointer parameters often have a NULL error code, meaning they + * could not perform any operation, because one of the required parameters was + * NULL. Some functions operate correctly or are defined as effectless on NULL. + * + * Some functions additionally return a value outside their + * return type domain, or a bool containing true on success and false on + * failure. + * + * All functions that take a Tox instance pointer will cause undefined behaviour + * when passed a NULL Tox pointer. + * + * All integer values are expected in host byte order. + * + * Functions with parameters with enum types cause unspecified behaviour if the + * enumeration value is outside the valid range of the type. If possible, the + * function will try to use a sane default, but there will be no error code, + * and one possible action for the function to take is to have no effect. + * + * Integer constants and the memory layout of publicly exposed structs are not + * part of the ABI. + */ + +/** \subsection events Events and callbacks + * + * Events are handled by callbacks. One callback can be registered per event. + * All events have a callback function type named `tox_{event}_cb` and a + * function to register it named `tox_callback_{event}`. Passing a NULL + * callback will result in no callback being registered for that event. Only + * one callback per event can be registered, so if a client needs multiple + * event listeners, it needs to implement the dispatch functionality itself. + * + * The last argument to a callback is the user data pointer. It is passed from + * ${tox.iterate} to each callback in sequence. + * + * The user data pointer is never stored or dereferenced by any library code, so + * can be any pointer, including NULL. Callbacks must all operate on the same + * object type. In the apidsl code (tox.in.h), this is denoted with `any`. The + * `any` in ${tox.iterate} must be the same `any` as in all callbacks. In C, + * lacking parametric polymorphism, this is a pointer to void. + * + * Old style callbacks that are registered together with a user data pointer + * receive that pointer as argument when they are called. They can each have + * their own user data pointer of their own type. + */ + +/** \subsection threading Threading implications + * + * It is possible to run multiple concurrent threads with a Tox instance for + * each thread. It is also possible to run all Tox instances in the same thread. + * A common way to run Tox (multiple or single instance) is to have one thread + * running a simple ${tox.iterate} loop, sleeping for ${tox.iteration_interval} + * milliseconds on each iteration. + * + * If you want to access a single Tox instance from multiple threads, access + * to the instance must be synchronised. While multiple threads can concurrently + * access multiple different Tox instances, no more than one API function can + * operate on a single instance at any given time. + * + * Functions that write to variable length byte arrays will always have a size + * function associated with them. The result of this size function is only valid + * until another mutating function (one that takes a pointer to non-const Tox) + * is called. Thus, clients must ensure that no other thread calls a mutating + * function between the call to the size function and the call to the retrieval + * function. + * + * E.g. to get the current nickname, one would write + * + * \code + * size_t length = ${tox.self.name.size}(tox); + * uint8_t *name = malloc(length); + * if (!name) abort(); + * ${tox.self.name.get}(tox, name); + * \endcode + * + * If any other thread calls ${tox.self.name.set} while this thread is allocating + * memory, the length may have become invalid, and the call to + * ${tox.self.name.get} may cause undefined behaviour. + */ + +// The rest of this file is in class tox. +class tox { + +/** + * The Tox instance type. All the state associated with a connection is held + * within the instance. Multiple instances can exist and operate concurrently. + * The maximum number of Tox instances that can exist on a single network + * device is limited. Note that this is not just a per-process limit, since the + * limiting factor is the number of usable ports on a device. + */ +struct this; + + +/******************************************************************************* + * + * :: API version + * + ******************************************************************************/ + + +/** + * The major version number. Incremented when the API or ABI changes in an + * incompatible way. + * + * The function variants of these constants return the version number of the + * library. They can be used to display the Tox library version or to check + * whether the client is compatible with the dynamically linked version of Tox. + */ +const VERSION_MAJOR = 0; + +/** + * The minor version number. Incremented when functionality is added without + * breaking the API or ABI. Set to 0 when the major version number is + * incremented. + */ +const VERSION_MINOR = 1; + +/** + * The patch or revision number. Incremented when bugfixes are applied without + * changing any functionality or API or ABI. + */ +const VERSION_PATCH = 10; + +/** + * A macro to check at preprocessing time whether the client code is compatible + * with the installed version of Tox. Leading zeros in the version number are + * ignored. E.g. 0.1.5 is to 0.1.4 what 1.5 is to 1.4, that is: it can add new + * features, but can't break the API. + */ +#define TOX_VERSION_IS_API_COMPATIBLE(MAJOR, MINOR, PATCH) \ + (TOX_VERSION_MAJOR > 0 && TOX_VERSION_MAJOR == MAJOR) && ( \ + /* 1.x.x, 2.x.x, etc. with matching major version. */ \ + TOX_VERSION_MINOR > MINOR || \ + TOX_VERSION_MINOR == MINOR && TOX_VERSION_PATCH >= PATCH \ + ) || (TOX_VERSION_MAJOR == 0 && MAJOR == 0) && ( \ + /* 0.x.x makes minor behave like major above. */ \ + (TOX_VERSION_MINOR > 0 && TOX_VERSION_MINOR == MINOR) && ( \ + TOX_VERSION_PATCH >= PATCH \ + ) || (TOX_VERSION_MINOR == 0 && MINOR == 0) && ( \ + /* 0.0.x and 0.0.y are only compatible if x == y. */ \ + TOX_VERSION_PATCH == PATCH \ + ) \ + ) + +static namespace version { + + /** + * Return whether the compiled library version is compatible with the passed + * version numbers. + */ + bool is_compatible(uint32_t major, uint32_t minor, uint32_t patch); + +} + +/** + * A convenience macro to call tox_version_is_compatible with the currently + * compiling API version. + */ +#define TOX_VERSION_IS_ABI_COMPATIBLE() \ + tox_version_is_compatible(TOX_VERSION_MAJOR, TOX_VERSION_MINOR, TOX_VERSION_PATCH) + +/******************************************************************************* + * + * :: Numeric constants + * + * The values of these are not part of the ABI. Prefer to use the function + * versions of them for code that should remain compatible with future versions + * of toxcore. + * + ******************************************************************************/ + + +/** + * The size of a Tox Public Key in bytes. + */ +const PUBLIC_KEY_SIZE = 32; + +/** + * The size of a Tox Secret Key in bytes. + */ +const SECRET_KEY_SIZE = 32; + +/** + * The size of the nospam in bytes when written in a Tox address. + */ +const NOSPAM_SIZE = sizeof(uint32_t); + +/** + * The size of a Tox address in bytes. Tox addresses are in the format + * [Public Key ($PUBLIC_KEY_SIZE bytes)][nospam (4 bytes)][checksum (2 bytes)]. + * + * The checksum is computed over the Public Key and the nospam value. The first + * byte is an XOR of all the even bytes (0, 2, 4, ...), the second byte is an + * XOR of all the odd bytes (1, 3, 5, ...) of the Public Key and nospam. + */ +const ADDRESS_SIZE = PUBLIC_KEY_SIZE + NOSPAM_SIZE + sizeof(uint16_t); + +/** + * Maximum length of a nickname in bytes. + */ +const MAX_NAME_LENGTH = 128; + +/** + * Maximum length of a status message in bytes. + */ +const MAX_STATUS_MESSAGE_LENGTH = 1007; + +/** + * Maximum length of a friend request message in bytes. + */ +const MAX_FRIEND_REQUEST_LENGTH = 1016; + +/** + * Maximum length of a single message after which it should be split. + */ +const MAX_MESSAGE_LENGTH = 1372; + +/** + * Maximum size of custom packets. TODO(iphydf): should be LENGTH? + */ +const MAX_CUSTOM_PACKET_SIZE = 1373; + +/** + * The number of bytes in a hash generated by $hash. + */ +const HASH_LENGTH = 32; + +/** + * The number of bytes in a file id. + */ +const FILE_ID_LENGTH = 32; + +/** + * Maximum file name length for file transfers. + */ +const MAX_FILENAME_LENGTH = 255; + + +/******************************************************************************* + * + * :: Global enumerations + * + ******************************************************************************/ + + +/** + * Represents the possible statuses a client can have. + */ +enum class USER_STATUS { + /** + * User is online and available. + */ + NONE, + /** + * User is away. Clients can set this e.g. after a user defined + * inactivity time. + */ + AWAY, + /** + * User is busy. Signals to other clients that this client does not + * currently wish to communicate. + */ + BUSY, +} + + +/** + * Represents message types for ${tox.friend.send.message} and conference + * messages. + */ +enum class MESSAGE_TYPE { + /** + * Normal text message. Similar to PRIVMSG on IRC. + */ + NORMAL, + /** + * A message describing an user action. This is similar to /me (CTCP ACTION) + * on IRC. + */ + ACTION, +} + + +/******************************************************************************* + * + * :: Startup options + * + ******************************************************************************/ + + +/** + * Type of proxy used to connect to TCP relays. + */ +enum class PROXY_TYPE { + /** + * Don't use a proxy. + */ + NONE, + /** + * HTTP proxy using CONNECT. + */ + HTTP, + /** + * SOCKS proxy for simple socket pipes. + */ + SOCKS5, +} + +/** + * Type of savedata to create the Tox instance from. + */ +enum class SAVEDATA_TYPE { + /** + * No savedata. + */ + NONE, + /** + * Savedata is one that was obtained from ${savedata.get}. + */ + TOX_SAVE, + /** + * Savedata is a secret key of length $SECRET_KEY_SIZE. + */ + SECRET_KEY, +} + + +/** + * Severity level of log messages. + */ +enum class LOG_LEVEL { + /** + * Very detailed traces including all network activity. + */ + TRACE, + /** + * Debug messages such as which port we bind to. + */ + DEBUG, + /** + * Informational log messages such as video call status changes. + */ + INFO, + /** + * Warnings about internal inconsistency or logic errors. + */ + WARNING, + /** + * Severe unexpected errors caused by external or internal inconsistency. + */ + ERROR, +} + +/** + * This event is triggered when the toxcore library logs an internal message. + * This is mostly useful for debugging. This callback can be called from any + * function, not just $iterate. This means the user data lifetime must at + * least extend between registering and unregistering it or $kill. + * + * Other toxcore modules such as toxav may concurrently call this callback at + * any time. Thus, user code must make sure it is equipped to handle concurrent + * execution, e.g. by employing appropriate mutex locking. + * + * @param level The severity of the log message. + * @param file The source file from which the message originated. + * @param line The source line from which the message originated. + * @param func The function from which the message originated. + * @param message The log message. + * @param user_data The user data pointer passed to $new in options. + */ +typedef void log_cb(LOG_LEVEL level, string file, uint32_t line, string func, string message, any user_data); + + +static class options { + /** + * This struct contains all the startup options for Tox. You must $new to + * allocate an object of this type. + * + * WARNING: Although this struct happens to be visible in the API, it is + * effectively private. Do not allocate this yourself or access members + * directly, as it *will* break binary compatibility frequently. + * + * @deprecated The memory layout of this struct (size, alignment, and field + * order) is not part of the ABI. To remain compatible, prefer to use $new to + * allocate the object and accessor functions to set the members. The struct + * will become opaque (i.e. the definition will become private) in v0.2.0. + */ + struct this [get, set] { + /** + * The type of socket to create. + * + * If this is set to false, an IPv4 socket is created, which subsequently + * only allows IPv4 communication. + * If it is set to true, an IPv6 socket is created, allowing both IPv4 and + * IPv6 communication. + */ + bool ipv6_enabled; + + /** + * Enable the use of UDP communication when available. + * + * Setting this to false will force Tox to use TCP only. Communications will + * need to be relayed through a TCP relay node, potentially slowing them down. + * Disabling UDP support is necessary when using anonymous proxies or Tor. + */ + bool udp_enabled; + + /** + * Enable local network peer discovery. + * + * Disabling this will cause Tox to not look for peers on the local network. + */ + bool local_discovery_enabled; + + namespace proxy { + /** + * Pass communications through a proxy. + */ + PROXY_TYPE type; + + /** + * The IP address or DNS name of the proxy to be used. + * + * If used, this must be non-NULL and be a valid DNS name. The name must not + * exceed 255 characters, and be in a NUL-terminated C string format + * (255 chars + 1 NUL byte). + * + * This member is ignored (it can be NULL) if proxy_type is ${PROXY_TYPE.NONE}. + * + * The data pointed at by this member is owned by the user, so must + * outlive the options object. + */ + string host; + + /** + * The port to use to connect to the proxy server. + * + * Ports must be in the range (1, 65535). The value is ignored if + * proxy_type is ${PROXY_TYPE.NONE}. + */ + uint16_t port; + } + + /** + * The start port of the inclusive port range to attempt to use. + * + * If both start_port and end_port are 0, the default port range will be + * used: [33445, 33545]. + * + * If either start_port or end_port is 0 while the other is non-zero, the + * non-zero port will be the only port in the range. + * + * Having start_port > end_port will yield the same behavior as if start_port + * and end_port were swapped. + */ + uint16_t start_port; + + /** + * The end port of the inclusive port range to attempt to use. + */ + uint16_t end_port; + + /** + * The port to use for the TCP server (relay). If 0, the TCP server is + * disabled. + * + * Enabling it is not required for Tox to function properly. + * + * When enabled, your Tox instance can act as a TCP relay for other Tox + * instance. This leads to increased traffic, thus when writing a client + * it is recommended to enable TCP server only if the user has an option + * to disable it. + */ + uint16_t tcp_port; + + /** + * Enables or disables UDP hole-punching in toxcore. (Default: enabled). + */ + bool hole_punching_enabled; + + namespace savedata { + /** + * The type of savedata to load from. + */ + SAVEDATA_TYPE type; + + /** + * The savedata. + * + * The data pointed at by this member is owned by the user, so must + * outlive the options object. + */ + const uint8_t[length] data; + + /** + * The length of the savedata. + */ + size_t length; + } + + namespace log { + /** + * Logging callback for the new tox instance. + */ + log_cb *callback; + + /** + * User data pointer passed to the logging callback. + */ + any user_data; + } + } + + + /** + * Initialises a $this object with the default options. + * + * The result of this function is independent of the original options. All + * values will be overwritten, no values will be read (so it is permissible + * to pass an uninitialised object). + * + * If options is NULL, this function has no effect. + * + * @param options An options object to be filled with default options. + */ + void default(); + + + /** + * Allocates a new $this object and initialises it with the default + * options. This function can be used to preserve long term ABI compatibility by + * giving the responsibility of allocation and deallocation to the Tox library. + * + * Objects returned from this function must be freed using the $free + * function. + * + * @return A new $this object with default options or NULL on failure. + */ + static this new() { + /** + * The function failed to allocate enough memory for the options struct. + */ + MALLOC, + } + + + /** + * Releases all resources associated with an options objects. + * + * Passing a pointer that was not returned by $new results in + * undefined behaviour. + */ + void free(); +} + + +/******************************************************************************* + * + * :: Creation and destruction + * + ******************************************************************************/ + + +/** + * @brief Creates and initialises a new Tox instance with the options passed. + * + * This function will bring the instance into a valid state. Running the event + * loop with a new instance will operate correctly. + * + * If loading failed or succeeded only partially, the new or partially loaded + * instance is returned and an error code is set. + * + * @param options An options object as described above. If this parameter is + * NULL, the default options are used. + * + * @see $iterate for the event loop. + * + * @return A new Tox instance pointer on success or NULL on failure. + */ +static this new(const options_t *options) { + NULL, + /** + * The function was unable to allocate enough memory to store the internal + * structures for the Tox object. + */ + MALLOC, + /** + * The function was unable to bind to a port. This may mean that all ports + * have already been bound, e.g. by other Tox instances, or it may mean + * a permission error. You may be able to gather more information from errno. + */ + PORT_ALLOC, + + namespace PROXY { + /** + * proxy_type was invalid. + */ + BAD_TYPE, + /** + * proxy_type was valid but the proxy_host passed had an invalid format + * or was NULL. + */ + BAD_HOST, + /** + * proxy_type was valid, but the proxy_port was invalid. + */ + BAD_PORT, + /** + * The proxy address passed could not be resolved. + */ + NOT_FOUND, + } + + namespace LOAD { + /** + * The byte array to be loaded contained an encrypted save. + */ + ENCRYPTED, + /** + * The data format was invalid. This can happen when loading data that was + * saved by an older version of Tox, or when the data has been corrupted. + * When loading from badly formatted data, some data may have been loaded, + * and the rest is discarded. Passing an invalid length parameter also + * causes this error. + */ + BAD_FORMAT, + } +} + + +/** + * Releases all resources associated with the Tox instance and disconnects from + * the network. + * + * After calling this function, the Tox pointer becomes invalid. No other + * functions can be called, and the pointer value can no longer be read. + */ +void kill(); + + +uint8_t[size] savedata { + /** + * Calculates the number of bytes required to store the tox instance with + * $get. This function cannot fail. The result is always greater than 0. + * + * @see threading for concurrency implications. + */ + size(); + + /** + * Store all information associated with the tox instance to a byte array. + * + * @param savedata A memory region large enough to store the tox instance + * data. Call $size to find the number of bytes required. If this parameter + * is NULL, this function has no effect. + */ + get(); +} + + +/******************************************************************************* + * + * :: Connection lifecycle and event loop + * + ******************************************************************************/ + + +/** + * Sends a "get nodes" request to the given bootstrap node with IP, port, and + * public key to setup connections. + * + * This function will attempt to connect to the node using UDP. You must use + * this function even if ${options.this.udp_enabled} was set to false. + * + * @param address The hostname or IP address (IPv4 or IPv6) of the node. + * @param port The port on the host on which the bootstrap Tox instance is + * listening. + * @param public_key The long term public key of the bootstrap node + * ($PUBLIC_KEY_SIZE bytes). + * @return true on success. + */ +bool bootstrap(string address, uint16_t port, const uint8_t[PUBLIC_KEY_SIZE] public_key) { + NULL, + /** + * The address could not be resolved to an IP address, or the IP address + * passed was invalid. + */ + BAD_HOST, + /** + * The port passed was invalid. The valid port range is (1, 65535). + */ + BAD_PORT, +} + + +/** + * Adds additional host:port pair as TCP relay. + * + * This function can be used to initiate TCP connections to different ports on + * the same bootstrap node, or to add TCP relays without using them as + * bootstrap nodes. + * + * @param address The hostname or IP address (IPv4 or IPv6) of the TCP relay. + * @param port The port on the host on which the TCP relay is listening. + * @param public_key The long term public key of the TCP relay + * ($PUBLIC_KEY_SIZE bytes). + * @return true on success. + */ +bool add_tcp_relay(string address, uint16_t port, const uint8_t[PUBLIC_KEY_SIZE] public_key) + with error for bootstrap; + + +/** + * Protocols that can be used to connect to the network or friends. + */ +enum class CONNECTION { + /** + * There is no connection. This instance, or the friend the state change is + * about, is now offline. + */ + NONE, + /** + * A TCP connection has been established. For the own instance, this means it + * is connected through a TCP relay, only. For a friend, this means that the + * connection to that particular friend goes through a TCP relay. + */ + TCP, + /** + * A UDP connection has been established. For the own instance, this means it + * is able to send UDP packets to DHT nodes, but may still be connected to + * a TCP relay. For a friend, this means that the connection to that + * particular friend was built using direct UDP packets. + */ + UDP, +} + + +inline namespace self { + + CONNECTION connection_status { + /** + * Return whether we are connected to the DHT. The return value is equal to the + * last value received through the `${event connection_status}` callback. + */ + get(); + } + + + /** + * This event is triggered whenever there is a change in the DHT connection + * state. When disconnected, a client may choose to call $bootstrap again, to + * reconnect to the DHT. Note that this state may frequently change for short + * amounts of time. Clients should therefore not immediately bootstrap on + * receiving a disconnect. + * + * TODO(iphydf): how long should a client wait before bootstrapping again? + */ + event connection_status const { + /** + * @param connection_status Whether we are connected to the DHT. + */ + typedef void(CONNECTION connection_status); + } + +} + + +/** + * Return the time in milliseconds before $iterate() should be called again + * for optimal performance. + */ +const uint32_t iteration_interval(); + + +/** + * The main loop that needs to be run in intervals of $iteration_interval() + * milliseconds. + */ +void iterate(any user_data); + + +/******************************************************************************* + * + * :: Internal client information (Tox address/id) + * + ******************************************************************************/ + + +inline namespace self { + + uint8_t[ADDRESS_SIZE] address { + /** + * Writes the Tox friend address of the client to a byte array. The address is + * not in human-readable format. If a client wants to display the address, + * formatting is required. + * + * @param address A memory region of at least $ADDRESS_SIZE bytes. If this + * parameter is NULL, this function has no effect. + * @see $ADDRESS_SIZE for the address format. + */ + get(); + } + + + uint32_t nospam { + /** + * Set the 4-byte nospam part of the address. This value is expected in host + * byte order. I.e. 0x12345678 will form the bytes [12, 34, 56, 78] in the + * nospam part of the Tox friend address. + * + * @param nospam Any 32 bit unsigned integer. + */ + set(); + + /** + * Get the 4-byte nospam part of the address. This value is returned in host + * byte order. + */ + get(); + } + + + uint8_t[PUBLIC_KEY_SIZE] public_key { + /** + * Copy the Tox Public Key (long term) from the Tox object. + * + * @param public_key A memory region of at least $PUBLIC_KEY_SIZE bytes. If + * this parameter is NULL, this function has no effect. + */ + get(); + } + + + uint8_t[SECRET_KEY_SIZE] secret_key { + /** + * Copy the Tox Secret Key from the Tox object. + * + * @param secret_key A memory region of at least $SECRET_KEY_SIZE bytes. If + * this parameter is NULL, this function has no effect. + */ + get(); + } + +} + + +/******************************************************************************* + * + * :: User-visible client information (nickname/status) + * + ******************************************************************************/ + + +/** + * Common error codes for all functions that set a piece of user-visible + * client information. + */ +error for set_info { + NULL, + /** + * Information length exceeded maximum permissible size. + */ + TOO_LONG, +} + + +inline namespace self { + + uint8_t[length <= MAX_NAME_LENGTH] name { + /** + * Set the nickname for the Tox client. + * + * Nickname length cannot exceed $MAX_NAME_LENGTH. If length is 0, the name + * parameter is ignored (it can be NULL), and the nickname is set back to empty. + * + * @param name A byte array containing the new nickname. + * @param length The size of the name byte array. + * + * @return true on success. + */ + set() with error for set_info; + + /** + * Return the length of the current nickname as passed to $set. + * + * If no nickname was set before calling this function, the name is empty, + * and this function returns 0. + * + * @see threading for concurrency implications. + */ + size(); + + /** + * Write the nickname set by $set to a byte array. + * + * If no nickname was set before calling this function, the name is empty, + * and this function has no effect. + * + * Call $size to find out how much memory to allocate for + * the result. + * + * @param name A valid memory location large enough to hold the nickname. + * If this parameter is NULL, the function has no effect. + */ + get(); + } + + + uint8_t[length <= MAX_STATUS_MESSAGE_LENGTH] status_message { + /** + * Set the client's status message. + * + * Status message length cannot exceed $MAX_STATUS_MESSAGE_LENGTH. If + * length is 0, the status parameter is ignored (it can be NULL), and the + * user status is set back to empty. + */ + set() with error for set_info; + + /** + * Return the length of the current status message as passed to $set. + * + * If no status message was set before calling this function, the status + * is empty, and this function returns 0. + * + * @see threading for concurrency implications. + */ + size(); + + /** + * Write the status message set by $set to a byte array. + * + * If no status message was set before calling this function, the status is + * empty, and this function has no effect. + * + * Call $size to find out how much memory to allocate for + * the result. + * + * @param status_message A valid memory location large enough to hold the + * status message. If this parameter is NULL, the function has no effect. + */ + get(); + } + + + USER_STATUS status { + /** + * Set the client's user status. + * + * @param status One of the user statuses listed in the enumeration above. + */ + set(); + + /** + * Returns the client's user status. + */ + get(); + } + +} + + +/******************************************************************************* + * + * :: Friend list management + * + ******************************************************************************/ + + +namespace friend { + + /** + * Add a friend to the friend list and send a friend request. + * + * A friend request message must be at least 1 byte long and at most + * $MAX_FRIEND_REQUEST_LENGTH. + * + * Friend numbers are unique identifiers used in all functions that operate on + * friends. Once added, a friend number is stable for the lifetime of the Tox + * object. After saving the state and reloading it, the friend numbers may not + * be the same as before. Deleting a friend creates a gap in the friend number + * set, which is filled by the next adding of a friend. Any pattern in friend + * numbers should not be relied on. + * + * If more than INT32_MAX friends are added, this function causes undefined + * behaviour. + * + * @param address The address of the friend (returned by ${self.address.get} of + * the friend you wish to add) it must be $ADDRESS_SIZE bytes. + * @param message The message that will be sent along with the friend request. + * @param length The length of the data byte array. + * + * @return the friend number on success, UINT32_MAX on failure. + */ + uint32_t add( + const uint8_t[ADDRESS_SIZE] address, + const uint8_t[length <= MAX_FRIEND_REQUEST_LENGTH] message + ) { + NULL, + /** + * The length of the friend request message exceeded + * $MAX_FRIEND_REQUEST_LENGTH. + */ + TOO_LONG, + /** + * The friend request message was empty. This, and the TOO_LONG code will + * never be returned from $add_norequest. + */ + NO_MESSAGE, + /** + * The friend address belongs to the sending client. + */ + OWN_KEY, + /** + * A friend request has already been sent, or the address belongs to a friend + * that is already on the friend list. + */ + ALREADY_SENT, + /** + * The friend address checksum failed. + */ + BAD_CHECKSUM, + /** + * The friend was already there, but the nospam value was different. + */ + SET_NEW_NOSPAM, + /** + * A memory allocation failed when trying to increase the friend list size. + */ + MALLOC, + } + + + /** + * Add a friend without sending a friend request. + * + * This function is used to add a friend in response to a friend request. If the + * client receives a friend request, it can be reasonably sure that the other + * client added this client as a friend, eliminating the need for a friend + * request. + * + * This function is also useful in a situation where both instances are + * controlled by the same entity, so that this entity can perform the mutual + * friend adding. In this case, there is no need for a friend request, either. + * + * @param public_key A byte array of length $PUBLIC_KEY_SIZE containing the + * Public Key (not the Address) of the friend to add. + * + * @return the friend number on success, UINT32_MAX on failure. + * @see $add for a more detailed description of friend numbers. + */ + uint32_t add_norequest(const uint8_t[PUBLIC_KEY_SIZE] public_key) + with error for add; + + + /** + * Remove a friend from the friend list. + * + * This does not notify the friend of their deletion. After calling this + * function, this client will appear offline to the friend and no communication + * can occur between the two. + * + * @param friend_number Friend number for the friend to be deleted. + * + * @return true on success. + */ + bool delete(uint32_t friend_number) { + /** + * There was no friend with the given friend number. No friends were deleted. + */ + FRIEND_NOT_FOUND, + } + +} + + +/******************************************************************************* + * + * :: Friend list queries + * + ******************************************************************************/ + +namespace friend { + + /** + * Return the friend number associated with that Public Key. + * + * @return the friend number on success, UINT32_MAX on failure. + * @param public_key A byte array containing the Public Key. + */ + const uint32_t by_public_key(const uint8_t[PUBLIC_KEY_SIZE] public_key) { + NULL, + /** + * No friend with the given Public Key exists on the friend list. + */ + NOT_FOUND, + } + + + /** + * Checks if a friend with the given friend number exists and returns true if + * it does. + */ + const bool exists(uint32_t friend_number); + +} + +inline namespace self { + + uint32_t[size] friend_list { + /** + * Return the number of friends on the friend list. + * + * This function can be used to determine how much memory to allocate for + * $get. + */ + size(); + + + /** + * Copy a list of valid friend numbers into an array. + * + * Call $size to determine the number of elements to allocate. + * + * @param friend_list A memory region with enough space to hold the friend + * list. If this parameter is NULL, this function has no effect. + */ + get(); + } + +} + + + +namespace friend { + + uint8_t[PUBLIC_KEY_SIZE] public_key { + /** + * Copies the Public Key associated with a given friend number to a byte array. + * + * @param friend_number The friend number you want the Public Key of. + * @param public_key A memory region of at least $PUBLIC_KEY_SIZE bytes. If + * this parameter is NULL, this function has no effect. + * + * @return true on success. + */ + get(uint32_t friend_number) { + /** + * No friend with the given number exists on the friend list. + */ + FRIEND_NOT_FOUND, + } + } + +} + +namespace friend { + + uint64_t last_online { + /** + * Return a unix-time timestamp of the last time the friend associated with a given + * friend number was seen online. This function will return UINT64_MAX on error. + * + * @param friend_number The friend number you want to query. + */ + get(uint32_t friend_number) { + /** + * No friend with the given number exists on the friend list. + */ + FRIEND_NOT_FOUND, + } + } + +} + +/******************************************************************************* + * + * :: Friend-specific state queries (can also be received through callbacks) + * + ******************************************************************************/ + + +namespace friend { + + /** + * Common error codes for friend state query functions. + */ + error for query { + /** + * The pointer parameter for storing the query result (name, message) was + * NULL. Unlike the `_self_` variants of these functions, which have no effect + * when a parameter is NULL, these functions return an error in that case. + */ + NULL, + /** + * The friend_number did not designate a valid friend. + */ + FRIEND_NOT_FOUND, + } + + + uint8_t[length <= MAX_NAME_LENGTH] name { + /** + * Return the length of the friend's name. If the friend number is invalid, the + * return value is unspecified. + * + * The return value is equal to the `length` argument received by the last + * `${event name}` callback. + */ + size(uint32_t friend_number) + with error for query; + + /** + * Write the name of the friend designated by the given friend number to a byte + * array. + * + * Call $size to determine the allocation size for the `name` + * parameter. + * + * The data written to `name` is equal to the data received by the last + * `${event name}` callback. + * + * @param name A valid memory region large enough to store the friend's name. + * + * @return true on success. + */ + get(uint32_t friend_number) + with error for query; + } + + + /** + * This event is triggered when a friend changes their name. + */ + event name const { + /** + * @param friend_number The friend number of the friend whose name changed. + * @param name A byte array containing the same data as + * ${name.get} would write to its `name` parameter. + * @param length A value equal to the return value of + * ${name.size}. + */ + typedef void(uint32_t friend_number, const uint8_t[length <= MAX_NAME_LENGTH] name); + } + + + uint8_t[length <= MAX_STATUS_MESSAGE_LENGTH] status_message { + /** + * Return the length of the friend's status message. If the friend number is + * invalid, the return value is SIZE_MAX. + */ + size(uint32_t friend_number) + with error for query; + + /** + * Write the status message of the friend designated by the given friend number to a byte + * array. + * + * Call $size to determine the allocation size for the `status_name` + * parameter. + * + * The data written to `status_message` is equal to the data received by the last + * `${event status_message}` callback. + * + * @param status_message A valid memory region large enough to store the friend's status message. + */ + get(uint32_t friend_number) + with error for query; + } + + + /** + * This event is triggered when a friend changes their status message. + */ + event status_message const { + /** + * @param friend_number The friend number of the friend whose status message + * changed. + * @param message A byte array containing the same data as + * ${status_message.get} would write to its `status_message` parameter. + * @param length A value equal to the return value of + * ${status_message.size}. + */ + typedef void(uint32_t friend_number, const uint8_t[length <= MAX_STATUS_MESSAGE_LENGTH] message); + } + + + USER_STATUS status { + /** + * Return the friend's user status (away/busy/...). If the friend number is + * invalid, the return value is unspecified. + * + * The status returned is equal to the last status received through the + * `${event status}` callback. + */ + get(uint32_t friend_number) + with error for query; + } + + + /** + * This event is triggered when a friend changes their user status. + */ + event status const { + /** + * @param friend_number The friend number of the friend whose user status + * changed. + * @param status The new user status. + */ + typedef void(uint32_t friend_number, USER_STATUS status); + } + + + CONNECTION connection_status { + /** + * Check whether a friend is currently connected to this client. + * + * The result of this function is equal to the last value received by the + * `${event connection_status}` callback. + * + * @param friend_number The friend number for which to query the connection + * status. + * + * @return the friend's connection status as it was received through the + * `${event connection_status}` event. + */ + get(uint32_t friend_number) + with error for query; + } + + + /** + * This event is triggered when a friend goes offline after having been online, + * or when a friend goes online. + * + * This callback is not called when adding friends. It is assumed that when + * adding friends, their connection status is initially offline. + */ + event connection_status const { + /** + * @param friend_number The friend number of the friend whose connection status + * changed. + * @param connection_status The result of calling + * ${connection_status.get} on the passed friend_number. + */ + typedef void(uint32_t friend_number, CONNECTION connection_status); + } + + + bool typing { + /** + * Check whether a friend is currently typing a message. + * + * @param friend_number The friend number for which to query the typing status. + * + * @return true if the friend is typing. + * @return false if the friend is not typing, or the friend number was + * invalid. Inspect the error code to determine which case it is. + */ + get(uint32_t friend_number) + with error for query; + } + + + /** + * This event is triggered when a friend starts or stops typing. + */ + event typing const { + /** + * @param friend_number The friend number of the friend who started or stopped + * typing. + * @param is_typing The result of calling ${typing.get} on the passed + * friend_number. + */ + typedef void(uint32_t friend_number, bool is_typing); + } + +} + + +/******************************************************************************* + * + * :: Sending private messages + * + ******************************************************************************/ + + +inline namespace self { + + bool typing { + /** + * Set the client's typing status for a friend. + * + * The client is responsible for turning it on or off. + * + * @param friend_number The friend to which the client is typing a message. + * @param typing The typing status. True means the client is typing. + * + * @return true on success. + */ + set(uint32_t friend_number) { + /** + * The friend number did not designate a valid friend. + */ + FRIEND_NOT_FOUND, + } + } + +} + + +namespace friend { + + namespace send { + + /** + * Send a text chat message to an online friend. + * + * This function creates a chat message packet and pushes it into the send + * queue. + * + * The message length may not exceed $MAX_MESSAGE_LENGTH. Larger messages + * must be split by the client and sent as separate messages. Other clients can + * then reassemble the fragments. Messages may not be empty. + * + * The return value of this function is the message ID. If a read receipt is + * received, the triggered `${event read_receipt}` event will be passed this message ID. + * + * Message IDs are unique per friend. The first message ID is 0. Message IDs are + * incremented by 1 each time a message is sent. If UINT32_MAX messages were + * sent, the next message ID is 0. + * + * @param type Message type (normal, action, ...). + * @param friend_number The friend number of the friend to send the message to. + * @param message A non-NULL pointer to the first element of a byte array + * containing the message text. + * @param length Length of the message to be sent. + */ + uint32_t message(uint32_t friend_number, MESSAGE_TYPE type, + const uint8_t[length <= MAX_MESSAGE_LENGTH] message) { + NULL, + /** + * The friend number did not designate a valid friend. + */ + FRIEND_NOT_FOUND, + /** + * This client is currently not connected to the friend. + */ + FRIEND_NOT_CONNECTED, + /** + * An allocation error occurred while increasing the send queue size. + */ + SENDQ, + /** + * Message length exceeded $MAX_MESSAGE_LENGTH. + */ + TOO_LONG, + /** + * Attempted to send a zero-length message. + */ + EMPTY, + } + + } + + + /** + * This event is triggered when the friend receives the message sent with + * ${send.message} with the corresponding message ID. + */ + event read_receipt const { + /** + * @param friend_number The friend number of the friend who received the message. + * @param message_id The message ID as returned from ${send.message} + * corresponding to the message sent. + */ + typedef void(uint32_t friend_number, uint32_t message_id); + } + +} + + +/******************************************************************************* + * + * :: Receiving private messages and friend requests + * + ******************************************************************************/ + + +namespace friend { + + /** + * This event is triggered when a friend request is received. + */ + event request const { + /** + * @param public_key The Public Key of the user who sent the friend request. + * @param message The message they sent along with the request. + * @param length The size of the message byte array. + */ + typedef void(const uint8_t[PUBLIC_KEY_SIZE] public_key, + const uint8_t[length <= MAX_MESSAGE_LENGTH] message); + } + + + /** + * This event is triggered when a message from a friend is received. + */ + event message const { + /** + * @param friend_number The friend number of the friend who sent the message. + * @param message The message data they sent. + * @param length The size of the message byte array. + */ + typedef void(uint32_t friend_number, MESSAGE_TYPE type, + const uint8_t[length <= MAX_MESSAGE_LENGTH] message); + } + +} + + +/******************************************************************************* + * + * :: File transmission: common between sending and receiving + * + ******************************************************************************/ + + +/** + * Generates a cryptographic hash of the given data. + * + * This function may be used by clients for any purpose, but is provided + * primarily for validating cached avatars. This use is highly recommended to + * avoid unnecessary avatar updates. + * + * If hash is NULL or data is NULL while length is not 0 the function returns false, + * otherwise it returns true. + * + * This function is a wrapper to internal message-digest functions. + * + * @param hash A valid memory location the hash data. It must be at least + * $HASH_LENGTH bytes in size. + * @param data Data to be hashed or NULL. + * @param length Size of the data array or 0. + * + * @return true if hash was not NULL. + */ +static bool hash(uint8_t[HASH_LENGTH] hash, const uint8_t[length] data); + + +namespace file { + + enum KIND { + /** + * Arbitrary file data. Clients can choose to handle it based on the file name + * or magic or any other way they choose. + */ + DATA, + /** + * Avatar file_id. This consists of $hash(image). + * Avatar data. This consists of the image data. + * + * Avatars can be sent at any time the client wishes. Generally, a client will + * send the avatar to a friend when that friend comes online, and to all + * friends when the avatar changed. A client can save some traffic by + * remembering which friend received the updated avatar already and only send + * it if the friend has an out of date avatar. + * + * Clients who receive avatar send requests can reject it (by sending + * ${CONTROL.CANCEL} before any other controls), or accept it (by + * sending ${CONTROL.RESUME}). The file_id of length $HASH_LENGTH bytes + * (same length as $FILE_ID_LENGTH) will contain the hash. A client can compare + * this hash with a saved hash and send ${CONTROL.CANCEL} to terminate the avatar + * transfer if it matches. + * + * When file_size is set to 0 in the transfer request it means that the client + * has no avatar. + */ + AVATAR, + } + + + enum class CONTROL { + /** + * Sent by the receiving side to accept a file send request. Also sent after a + * $PAUSE command to continue sending or receiving. + */ + RESUME, + /** + * Sent by clients to pause the file transfer. The initial state of a file + * transfer is always paused on the receiving side and running on the sending + * side. If both the sending and receiving side pause the transfer, then both + * need to send $RESUME for the transfer to resume. + */ + PAUSE, + /** + * Sent by the receiving side to reject a file send request before any other + * commands are sent. Also sent by either side to terminate a file transfer. + */ + CANCEL, + } + + + /** + * Sends a file control command to a friend for a given file transfer. + * + * @param friend_number The friend number of the friend the file is being + * transferred to or received from. + * @param file_number The friend-specific identifier for the file transfer. + * @param control The control command to send. + * + * @return true on success. + */ + bool control(uint32_t friend_number, uint32_t file_number, CONTROL control) { + /** + * The friend_number passed did not designate a valid friend. + */ + FRIEND_NOT_FOUND, + /** + * This client is currently not connected to the friend. + */ + FRIEND_NOT_CONNECTED, + /** + * No file transfer with the given file number was found for the given friend. + */ + NOT_FOUND, + /** + * A RESUME control was sent, but the file transfer is running normally. + */ + NOT_PAUSED, + /** + * A RESUME control was sent, but the file transfer was paused by the other + * party. Only the party that paused the transfer can resume it. + */ + DENIED, + /** + * A PAUSE control was sent, but the file transfer was already paused. + */ + ALREADY_PAUSED, + /** + * Packet queue is full. + */ + SENDQ, + } + + + /** + * This event is triggered when a file control command is received from a + * friend. + */ + event recv_control const { + /** + * When receiving ${CONTROL.CANCEL}, the client should release the + * resources associated with the file number and consider the transfer failed. + * + * @param friend_number The friend number of the friend who is sending the file. + * @param file_number The friend-specific file number the data received is + * associated with. + * @param control The file control command received. + */ + typedef void(uint32_t friend_number, uint32_t file_number, CONTROL control); + } + + /** + * Sends a file seek control command to a friend for a given file transfer. + * + * This function can only be called to resume a file transfer right before + * ${CONTROL.RESUME} is sent. + * + * @param friend_number The friend number of the friend the file is being + * received from. + * @param file_number The friend-specific identifier for the file transfer. + * @param position The position that the file should be seeked to. + */ + bool seek(uint32_t friend_number, uint32_t file_number, uint64_t position) { + /** + * The friend_number passed did not designate a valid friend. + */ + FRIEND_NOT_FOUND, + /** + * This client is currently not connected to the friend. + */ + FRIEND_NOT_CONNECTED, + /** + * No file transfer with the given file number was found for the given friend. + */ + NOT_FOUND, + /** + * File was not in a state where it could be seeked. + */ + DENIED, + /** + * Seek position was invalid + */ + INVALID_POSITION, + /** + * Packet queue is full. + */ + SENDQ, + } + + + error for get { + NULL, + /** + * The friend_number passed did not designate a valid friend. + */ + FRIEND_NOT_FOUND, + /** + * No file transfer with the given file number was found for the given friend. + */ + NOT_FOUND, + } + + uint8_t[FILE_ID_LENGTH] file_id { + /** + * Copy the file id associated to the file transfer to a byte array. + * + * @param friend_number The friend number of the friend the file is being + * transferred to or received from. + * @param file_number The friend-specific identifier for the file transfer. + * @param file_id A memory region of at least $FILE_ID_LENGTH bytes. If + * this parameter is NULL, this function has no effect. + * + * @return true on success. + */ + get(uint32_t friend_number, uint32_t file_number) + with error for get; + } + +} + + +/******************************************************************************* + * + * :: File transmission: sending + * + ******************************************************************************/ + + +namespace file { + + /** + * Send a file transmission request. + * + * Maximum filename length is $MAX_FILENAME_LENGTH bytes. The filename + * should generally just be a file name, not a path with directory names. + * + * If a non-UINT64_MAX file size is provided, it can be used by both sides to + * determine the sending progress. File size can be set to UINT64_MAX for streaming + * data of unknown size. + * + * File transmission occurs in chunks, which are requested through the + * `${event chunk_request}` event. + * + * When a friend goes offline, all file transfers associated with the friend are + * purged from core. + * + * If the file contents change during a transfer, the behaviour is unspecified + * in general. What will actually happen depends on the mode in which the file + * was modified and how the client determines the file size. + * + * - If the file size was increased + * - and sending mode was streaming (file_size = UINT64_MAX), the behaviour + * will be as expected. + * - and sending mode was file (file_size != UINT64_MAX), the + * ${event chunk_request} callback will receive length = 0 when Core thinks + * the file transfer has finished. If the client remembers the file size as + * it was when sending the request, it will terminate the transfer normally. + * If the client re-reads the size, it will think the friend cancelled the + * transfer. + * - If the file size was decreased + * - and sending mode was streaming, the behaviour is as expected. + * - and sending mode was file, the callback will return 0 at the new + * (earlier) end-of-file, signalling to the friend that the transfer was + * cancelled. + * - If the file contents were modified + * - at a position before the current read, the two files (local and remote) + * will differ after the transfer terminates. + * - at a position after the current read, the file transfer will succeed as + * expected. + * - In either case, both sides will regard the transfer as complete and + * successful. + * + * @param friend_number The friend number of the friend the file send request + * should be sent to. + * @param kind The meaning of the file to be sent. + * @param file_size Size in bytes of the file the client wants to send, UINT64_MAX if + * unknown or streaming. + * @param file_id A file identifier of length $FILE_ID_LENGTH that can be used to + * uniquely identify file transfers across core restarts. If NULL, a random one will + * be generated by core. It can then be obtained by using ${file_id.get}(). + * @param filename Name of the file. Does not need to be the actual name. This + * name will be sent along with the file send request. + * @param filename_length Size in bytes of the filename. + * + * @return A file number used as an identifier in subsequent callbacks. This + * number is per friend. File numbers are reused after a transfer terminates. + * On failure, this function returns UINT32_MAX. Any pattern in file numbers + * should not be relied on. + */ + uint32_t send(uint32_t friend_number, uint32_t kind, uint64_t file_size, + const uint8_t[FILE_ID_LENGTH] file_id, + const uint8_t[filename_length <= MAX_FILENAME_LENGTH] filename) { + NULL, + /** + * The friend_number passed did not designate a valid friend. + */ + FRIEND_NOT_FOUND, + /** + * This client is currently not connected to the friend. + */ + FRIEND_NOT_CONNECTED, + /** + * Filename length exceeded $MAX_FILENAME_LENGTH bytes. + */ + NAME_TOO_LONG, + /** + * Too many ongoing transfers. The maximum number of concurrent file transfers + * is 256 per friend per direction (sending and receiving). + */ + TOO_MANY, + } + + + /** + * Send a chunk of file data to a friend. + * + * This function is called in response to the `${event chunk_request}` callback. The + * length parameter should be equal to the one received though the callback. + * If it is zero, the transfer is assumed complete. For files with known size, + * Core will know that the transfer is complete after the last byte has been + * received, so it is not necessary (though not harmful) to send a zero-length + * chunk to terminate. For streams, core will know that the transfer is finished + * if a chunk with length less than the length requested in the callback is sent. + * + * @param friend_number The friend number of the receiving friend for this file. + * @param file_number The file transfer identifier returned by tox_file_send. + * @param position The file or stream position from which to continue reading. + * @return true on success. + */ + bool send_chunk(uint32_t friend_number, uint32_t file_number, uint64_t position, const uint8_t[length] data) { + /** + * The length parameter was non-zero, but data was NULL. + */ + NULL, + /** + * The friend_number passed did not designate a valid friend. + */ + FRIEND_NOT_FOUND, + /** + * This client is currently not connected to the friend. + */ + FRIEND_NOT_CONNECTED, + /** + * No file transfer with the given file number was found for the given friend. + */ + NOT_FOUND, + /** + * File transfer was found but isn't in a transferring state: (paused, done, + * broken, etc...) (happens only when not called from the request chunk callback). + */ + NOT_TRANSFERRING, + /** + * Attempted to send more or less data than requested. The requested data size is + * adjusted according to maximum transmission unit and the expected end of + * the file. Trying to send less or more than requested will return this error. + */ + INVALID_LENGTH, + /** + * Packet queue is full. + */ + SENDQ, + /** + * Position parameter was wrong. + */ + WRONG_POSITION, + } + + + /** + * This event is triggered when Core is ready to send more file data. + */ + event chunk_request const { + /** + * If the length parameter is 0, the file transfer is finished, and the client's + * resources associated with the file number should be released. After a call + * with zero length, the file number can be reused for future file transfers. + * + * If the requested position is not equal to the client's idea of the current + * file or stream position, it will need to seek. In case of read-once streams, + * the client should keep the last read chunk so that a seek back can be + * supported. A seek-back only ever needs to read from the last requested chunk. + * This happens when a chunk was requested, but the send failed. A seek-back + * request can occur an arbitrary number of times for any given chunk. + * + * In response to receiving this callback, the client should call the function + * `$send_chunk` with the requested chunk. If the number of bytes sent + * through that function is zero, the file transfer is assumed complete. A + * client must send the full length of data requested with this callback. + * + * @param friend_number The friend number of the receiving friend for this file. + * @param file_number The file transfer identifier returned by $send. + * @param position The file or stream position from which to continue reading. + * @param length The number of bytes requested for the current chunk. + */ + typedef void(uint32_t friend_number, uint32_t file_number, uint64_t position, size_t length); + } + +} + + +/******************************************************************************* + * + * :: File transmission: receiving + * + ******************************************************************************/ + + +namespace file { + + /** + * This event is triggered when a file transfer request is received. + */ + event recv const { + /** + * The client should acquire resources to be associated with the file transfer. + * Incoming file transfers start in the PAUSED state. After this callback + * returns, a transfer can be rejected by sending a ${CONTROL.CANCEL} + * control command before any other control commands. It can be accepted by + * sending ${CONTROL.RESUME}. + * + * @param friend_number The friend number of the friend who is sending the file + * transfer request. + * @param file_number The friend-specific file number the data received is + * associated with. + * @param kind The meaning of the file to be sent. + * @param file_size Size in bytes of the file the client wants to send, + * UINT64_MAX if unknown or streaming. + * @param filename Name of the file. Does not need to be the actual name. This + * name will be sent along with the file send request. + * @param filename_length Size in bytes of the filename. + */ + typedef void(uint32_t friend_number, uint32_t file_number, uint32_t kind, + uint64_t file_size, const uint8_t[filename_length <= MAX_FILENAME_LENGTH] filename); + } + + + /** + * This event is first triggered when a file transfer request is received, and + * subsequently when a chunk of file data for an accepted request was received. + */ + event recv_chunk const { + /** + * When length is 0, the transfer is finished and the client should release the + * resources it acquired for the transfer. After a call with length = 0, the + * file number can be reused for new file transfers. + * + * If position is equal to file_size (received in the file_receive callback) + * when the transfer finishes, the file was received completely. Otherwise, if + * file_size was UINT64_MAX, streaming ended successfully when length is 0. + * + * @param friend_number The friend number of the friend who is sending the file. + * @param file_number The friend-specific file number the data received is + * associated with. + * @param position The file position of the first byte in data. + * @param data A byte array containing the received chunk. + * @param length The length of the received chunk. + */ + typedef void(uint32_t friend_number, uint32_t file_number, uint64_t position, + const uint8_t[length] data); + } + +} + + +/******************************************************************************* + * + * :: Conference management + * + ******************************************************************************/ + +namespace conference { + + /** + * Conference types for the ${event invite} event. + */ + enum class TYPE { + /** + * Text-only conferences that must be accepted with the $join function. + */ + TEXT, + /** + * Video conference. The function to accept these is in toxav. + */ + AV, + } + + + /** + * This event is triggered when the client is invited to join a conference. + */ + event invite const { + /** + * The invitation will remain valid until the inviting friend goes offline + * or exits the conference. + * + * @param friend_number The friend who invited us. + * @param type The conference type (text only or audio/video). + * @param cookie A piece of data of variable length required to join the + * conference. + * @param length The length of the cookie. + */ + typedef void(uint32_t friend_number, TYPE type, const uint8_t[length] cookie); + } + + + /** + * This event is triggered when the client receives a conference message. + */ + event message const { + /** + * @param conference_number The conference number of the conference the message is intended for. + * @param peer_number The ID of the peer who sent the message. + * @param type The type of message (normal, action, ...). + * @param message The message data. + * @param length The length of the message. + */ + typedef void(uint32_t conference_number, uint32_t peer_number, MESSAGE_TYPE type, + const uint8_t[length] message); + } + + + /** + * This event is triggered when a peer changes the conference title. + * + * If peer_number == UINT32_MAX, then author is unknown (e.g. initial joining the conference). + */ + event title const { + /** + * @param conference_number The conference number of the conference the title change is intended for. + * @param peer_number The ID of the peer who changed the title. + * @param title The title data. + * @param length The title length. + */ + typedef void(uint32_t conference_number, uint32_t peer_number, const uint8_t[length] title); + } + + /** + * Peer list state change types. + */ + enum class STATE_CHANGE { + /** + * A peer has joined the conference. + */ + PEER_JOIN, + /** + * A peer has exited the conference. + */ + PEER_EXIT, + /** + * A peer has changed their name. + */ + PEER_NAME_CHANGE, + } + + /** + * This event is triggered when the peer list changes (name change, peer join, peer exit). + */ + event namelist_change const { + /** + * @param conference_number The conference number of the conference the title change is intended for. + * @param peer_number The ID of the peer who changed the title. + * @param change The type of change (one of $STATE_CHANGE). + */ + typedef void(uint32_t conference_number, uint32_t peer_number, STATE_CHANGE change); + } + + + /** + * Creates a new conference. + * + * This function creates a new text conference. + * + * @return conference number on success, or UINT32_MAX on failure. + */ + uint32_t new() { + /** + * The conference instance failed to initialize. + */ + INIT, + } + + /** + * This function deletes a conference. + * + * @param conference_number The conference number of the conference to be deleted. + * + * @return true on success. + */ + bool delete(uint32_t conference_number) { + /** + * The conference number passed did not designate a valid conference. + */ + CONFERENCE_NOT_FOUND, + } + + + namespace peer { + + /** + * Error codes for peer info queries. + */ + error for query { + /** + * The conference number passed did not designate a valid conference. + */ + CONFERENCE_NOT_FOUND, + /** + * The peer number passed did not designate a valid peer. + */ + PEER_NOT_FOUND, + /** + * The client is not connected to the conference. + */ + NO_CONNECTION, + } + + /** + * Return the number of peers in the conference. Return value is unspecified on failure. + */ + const uint32_t count(uint32_t conference_number) + with error for query; + + uint8_t[size] name { + + /** + * Return the length of the peer's name. Return value is unspecified on failure. + */ + size(uint32_t conference_number, uint32_t peer_number) + with error for query; + + /** + * Copy the name of peer_number who is in conference_number to name. + * name must be at least $MAX_NAME_LENGTH long. + * + * @return true on success. + */ + get(uint32_t conference_number, uint32_t peer_number) + with error for query; + } + + /** + * Copy the public key of peer_number who is in conference_number to public_key. + * public_key must be $PUBLIC_KEY_SIZE long. + * + * @return true on success. + */ + uint8_t[PUBLIC_KEY_SIZE] public_key { + get(uint32_t conference_number, uint32_t peer_number) + with error for query; + } + + /** + * Return true if passed peer_number corresponds to our own. + */ + const bool number_is_ours(uint32_t conference_number, uint32_t peer_number) + with error for query; + + } + + + /** + * Invites a friend to a conference. + * + * @param friend_number The friend number of the friend we want to invite. + * @param conference_number The conference number of the conference we want to invite the friend to. + * + * @return true on success. + */ + bool invite(uint32_t friend_number, uint32_t conference_number) { + /** + * The conference number passed did not designate a valid conference. + */ + CONFERENCE_NOT_FOUND, + /** + * The invite packet failed to send. + */ + FAIL_SEND, + } + + + /** + * Joins a conference that the client has been invited to. + * + * @param friend_number The friend number of the friend who sent the invite. + * @param cookie Received via the `${event invite}` event. + * @param length The size of cookie. + * + * @return conference number on success, UINT32_MAX on failure. + */ + uint32_t join(uint32_t friend_number, const uint8_t[length] cookie) { + /** + * The cookie passed has an invalid length. + */ + INVALID_LENGTH, + /** + * The conference is not the expected type. This indicates an invalid cookie. + */ + WRONG_TYPE, + /** + * The friend number passed does not designate a valid friend. + */ + FRIEND_NOT_FOUND, + /** + * Client is already in this conference. + */ + DUPLICATE, + /** + * Conference instance failed to initialize. + */ + INIT_FAIL, + /** + * The join packet failed to send. + */ + FAIL_SEND, + } + + + namespace send { + + /** + * Send a text chat message to the conference. + * + * This function creates a conference message packet and pushes it into the send + * queue. + * + * The message length may not exceed $MAX_MESSAGE_LENGTH. Larger messages + * must be split by the client and sent as separate messages. Other clients can + * then reassemble the fragments. + * + * @param conference_number The conference number of the conference the message is intended for. + * @param type Message type (normal, action, ...). + * @param message A non-NULL pointer to the first element of a byte array + * containing the message text. + * @param length Length of the message to be sent. + * + * @return true on success. + */ + bool message(uint32_t conference_number, MESSAGE_TYPE type, const uint8_t[length] message) { + /** + * The conference number passed did not designate a valid conference. + */ + CONFERENCE_NOT_FOUND, + /** + * The message is too long. + */ + TOO_LONG, + /** + * The client is not connected to the conference. + */ + NO_CONNECTION, + /** + * The message packet failed to send. + */ + FAIL_SEND, + } + } + + error for title { + /** + * The conference number passed did not designate a valid conference. + */ + CONFERENCE_NOT_FOUND, + /** + * The title is too long or empty. + */ + INVALID_LENGTH, + /** + * The title packet failed to send. + */ + FAIL_SEND, + } + + uint8_t[length <= MAX_NAME_LENGTH] title { + + /** + * Return the length of the conference title. Return value is unspecified on failure. + * + * The return value is equal to the `length` argument received by the last + * `${event title}` callback. + */ + size(uint32_t conference_number) + with error for title; + + /** + * Write the title designated by the given conference number to a byte array. + * + * Call $size to determine the allocation size for the `title` parameter. + * + * The data written to `title` is equal to the data received by the last + * `${event title}` callback. + * + * @param title A valid memory region large enough to store the title. + * If this parameter is NULL, this function has no effect. + * + * @return true on success. + */ + get(uint32_t conference_number) + with error for title; + + /** + * Set the conference title and broadcast it to the rest of the conference. + * + * Title length cannot be longer than $MAX_NAME_LENGTH. + * + * @return true on success. + */ + set(uint32_t conference_number) + with error for title; + } + + + uint32_t[size] chatlist { + /** + * Return the number of conferences in the Tox instance. + * This should be used to determine how much memory to allocate for `$get`. + */ + size(); + + /** + * Copy a list of valid conference IDs into the array chatlist. Determine how much space + * to allocate for the array with the `$size` function. + */ + get(); + } + + + /** + * Returns the type of conference ($TYPE) that conference_number is. Return value is + * unspecified on failure. + */ + TYPE type { + get(uint32_t conference_number) { + /** + * The conference number passed did not designate a valid conference. + */ + CONFERENCE_NOT_FOUND, + } + } + +} + + +/******************************************************************************* + * + * :: Low-level custom packet sending and receiving + * + ******************************************************************************/ + + +namespace friend { + + inline namespace send { + + error for custom_packet { + NULL, + /** + * The friend number did not designate a valid friend. + */ + FRIEND_NOT_FOUND, + /** + * This client is currently not connected to the friend. + */ + FRIEND_NOT_CONNECTED, + /** + * The first byte of data was not in the specified range for the packet type. + * This range is 200-254 for lossy, and 160-191 for lossless packets. + */ + INVALID, + /** + * Attempted to send an empty packet. + */ + EMPTY, + /** + * Packet data length exceeded $MAX_CUSTOM_PACKET_SIZE. + */ + TOO_LONG, + /** + * Packet queue is full. + */ + SENDQ, + } + + /** + * Send a custom lossy packet to a friend. + * + * The first byte of data must be in the range 200-254. Maximum length of a + * custom packet is $MAX_CUSTOM_PACKET_SIZE. + * + * Lossy packets behave like UDP packets, meaning they might never reach the + * other side or might arrive more than once (if someone is messing with the + * connection) or might arrive in the wrong order. + * + * Unless latency is an issue, it is recommended that you use lossless custom + * packets instead. + * + * @param friend_number The friend number of the friend this lossy packet + * should be sent to. + * @param data A byte array containing the packet data. + * @param length The length of the packet data byte array. + * + * @return true on success. + */ + bool lossy_packet(uint32_t friend_number, const uint8_t[length <= MAX_CUSTOM_PACKET_SIZE] data) + with error for custom_packet; + + + /** + * Send a custom lossless packet to a friend. + * + * The first byte of data must be in the range 160-191. Maximum length of a + * custom packet is $MAX_CUSTOM_PACKET_SIZE. + * + * Lossless packet behaviour is comparable to TCP (reliability, arrive in order) + * but with packets instead of a stream. + * + * @param friend_number The friend number of the friend this lossless packet + * should be sent to. + * @param data A byte array containing the packet data. + * @param length The length of the packet data byte array. + * + * @return true on success. + */ + bool lossless_packet(uint32_t friend_number, const uint8_t[length <= MAX_CUSTOM_PACKET_SIZE] data) + with error for custom_packet; + + } + + + event lossy_packet const { + /** + * @param friend_number The friend number of the friend who sent a lossy packet. + * @param data A byte array containing the received packet data. + * @param length The length of the packet data byte array. + */ + typedef void(uint32_t friend_number, const uint8_t[length <= MAX_CUSTOM_PACKET_SIZE] data); + } + + + event lossless_packet const { + /** + * @param friend_number The friend number of the friend who sent the packet. + * @param data A byte array containing the received packet data. + * @param length The length of the packet data byte array. + */ + typedef void(uint32_t friend_number, const uint8_t[length <= MAX_CUSTOM_PACKET_SIZE] data); + } + +} + + + +/******************************************************************************* + * + * :: Low-level network information + * + ******************************************************************************/ + + +inline namespace self { + + uint8_t[PUBLIC_KEY_SIZE] dht_id { + /** + * Writes the temporary DHT public key of this instance to a byte array. + * + * This can be used in combination with an externally accessible IP address and + * the bound port (from ${udp_port.get}) to run a temporary bootstrap node. + * + * Be aware that every time a new instance is created, the DHT public key + * changes, meaning this cannot be used to run a permanent bootstrap node. + * + * @param dht_id A memory region of at least $PUBLIC_KEY_SIZE bytes. If this + * parameter is NULL, this function has no effect. + */ + get(); + } + + + error for get_port { + /** + * The instance was not bound to any port. + */ + NOT_BOUND, + } + + + uint16_t udp_port { + /** + * Return the UDP port this Tox instance is bound to. + */ + get() with error for get_port; + } + + + uint16_t tcp_port { + /** + * Return the TCP port this Tox instance is bound to. This is only relevant if + * the instance is acting as a TCP relay. + */ + get() with error for get_port; + } + +} + +} // class tox + +%{ +#ifdef __cplusplus +} +#endif + +#endif +%} diff --git a/libs/libtox/src/toxcore/tox.c b/libs/libtox/src/toxcore/tox.c new file mode 100644 index 0000000000..12f3762083 --- /dev/null +++ b/libs/libtox/src/toxcore/tox.c @@ -0,0 +1,1551 @@ +/* + * The Tox public API. + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2013 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define _XOPEN_SOURCE 600 + +#define TOX_DEFINED +typedef struct Messenger Tox; +#include "tox.h" + +#include "Messenger.h" +#include "group.h" +#include "logger.h" + +#include "../toxencryptsave/defines.h" + +#define SET_ERROR_PARAMETER(param, x) {if(param) {*param = x;}} + +#if TOX_HASH_LENGTH != CRYPTO_SHA256_SIZE +#error TOX_HASH_LENGTH is assumed to be equal to CRYPTO_SHA256_SIZE +#endif + +#if FILE_ID_LENGTH != CRYPTO_SYMMETRIC_KEY_SIZE +#error FILE_ID_LENGTH is assumed to be equal to CRYPTO_SYMMETRIC_KEY_SIZE +#endif + +#if TOX_FILE_ID_LENGTH != CRYPTO_SYMMETRIC_KEY_SIZE +#error TOX_FILE_ID_LENGTH is assumed to be equal to CRYPTO_SYMMETRIC_KEY_SIZE +#endif + +#if TOX_FILE_ID_LENGTH != TOX_HASH_LENGTH +#error TOX_FILE_ID_LENGTH is assumed to be equal to TOX_HASH_LENGTH +#endif + +#if TOX_PUBLIC_KEY_SIZE != CRYPTO_PUBLIC_KEY_SIZE +#error TOX_PUBLIC_KEY_SIZE is assumed to be equal to CRYPTO_PUBLIC_KEY_SIZE +#endif + +#if TOX_SECRET_KEY_SIZE != CRYPTO_SECRET_KEY_SIZE +#error TOX_SECRET_KEY_SIZE is assumed to be equal to CRYPTO_SECRET_KEY_SIZE +#endif + +#if TOX_MAX_NAME_LENGTH != MAX_NAME_LENGTH +#error TOX_MAX_NAME_LENGTH is assumed to be equal to MAX_NAME_LENGTH +#endif + +#if TOX_MAX_STATUS_MESSAGE_LENGTH != MAX_STATUSMESSAGE_LENGTH +#error TOX_MAX_STATUS_MESSAGE_LENGTH is assumed to be equal to MAX_STATUSMESSAGE_LENGTH +#endif + + +bool tox_version_is_compatible(uint32_t major, uint32_t minor, uint32_t patch) +{ + return TOX_VERSION_IS_API_COMPATIBLE(major, minor, patch); +} + + +Tox *tox_new(const struct Tox_Options *options, TOX_ERR_NEW *error) +{ + Messenger_Options m_options = {0}; + + bool load_savedata_sk = 0, load_savedata_tox = 0; + + if (options == NULL) { + m_options.ipv6enabled = TOX_ENABLE_IPV6_DEFAULT; + } else { + if (tox_options_get_savedata_type(options) != TOX_SAVEDATA_TYPE_NONE) { + if (tox_options_get_savedata_data(options) == NULL || tox_options_get_savedata_length(options) == 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_NEW_LOAD_BAD_FORMAT); + return NULL; + } + } + + if (tox_options_get_savedata_type(options) == TOX_SAVEDATA_TYPE_SECRET_KEY) { + if (tox_options_get_savedata_length(options) != TOX_SECRET_KEY_SIZE) { + SET_ERROR_PARAMETER(error, TOX_ERR_NEW_LOAD_BAD_FORMAT); + return NULL; + } + + load_savedata_sk = 1; + } else if (tox_options_get_savedata_type(options) == TOX_SAVEDATA_TYPE_TOX_SAVE) { + if (tox_options_get_savedata_length(options) < TOX_ENC_SAVE_MAGIC_LENGTH) { + SET_ERROR_PARAMETER(error, TOX_ERR_NEW_LOAD_BAD_FORMAT); + return NULL; + } + + if (crypto_memcmp(tox_options_get_savedata_data(options), TOX_ENC_SAVE_MAGIC_NUMBER, TOX_ENC_SAVE_MAGIC_LENGTH) == 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_NEW_LOAD_ENCRYPTED); + return NULL; + } + + load_savedata_tox = 1; + } + + m_options.ipv6enabled = tox_options_get_ipv6_enabled(options); + m_options.udp_disabled = !tox_options_get_udp_enabled(options); + m_options.port_range[0] = tox_options_get_start_port(options); + m_options.port_range[1] = tox_options_get_end_port(options); + m_options.tcp_server_port = tox_options_get_tcp_port(options); + m_options.hole_punching_enabled = tox_options_get_hole_punching_enabled(options); + m_options.local_discovery_enabled = tox_options_get_local_discovery_enabled(options); + + m_options.log_callback = (logger_cb *)tox_options_get_log_callback(options); + m_options.log_user_data = tox_options_get_log_user_data(options); + + switch (tox_options_get_proxy_type(options)) { + case TOX_PROXY_TYPE_HTTP: + m_options.proxy_info.proxy_type = TCP_PROXY_HTTP; + break; + + case TOX_PROXY_TYPE_SOCKS5: + m_options.proxy_info.proxy_type = TCP_PROXY_SOCKS5; + break; + + case TOX_PROXY_TYPE_NONE: + m_options.proxy_info.proxy_type = TCP_PROXY_NONE; + break; + + default: + SET_ERROR_PARAMETER(error, TOX_ERR_NEW_PROXY_BAD_TYPE); + return NULL; + } + + if (m_options.proxy_info.proxy_type != TCP_PROXY_NONE) { + if (tox_options_get_proxy_port(options) == 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_NEW_PROXY_BAD_PORT); + return NULL; + } + + ip_init(&m_options.proxy_info.ip_port.ip, m_options.ipv6enabled); + + if (m_options.ipv6enabled) { + m_options.proxy_info.ip_port.ip.family = TOX_AF_UNSPEC; + } + + if (!addr_resolve_or_parse_ip(tox_options_get_proxy_host(options), &m_options.proxy_info.ip_port.ip, NULL)) { + SET_ERROR_PARAMETER(error, TOX_ERR_NEW_PROXY_BAD_HOST); + // TODO(irungentoo): TOX_ERR_NEW_PROXY_NOT_FOUND if domain. + return NULL; + } + + m_options.proxy_info.ip_port.port = net_htons(tox_options_get_proxy_port(options)); + } + } + + unsigned int m_error; + Messenger *m = new_messenger(&m_options, &m_error); + + if (!new_groupchats(m)) { + kill_messenger(m); + + if (m_error == MESSENGER_ERROR_PORT) { + SET_ERROR_PARAMETER(error, TOX_ERR_NEW_PORT_ALLOC); + } else if (m_error == MESSENGER_ERROR_TCP_SERVER) { + SET_ERROR_PARAMETER(error, TOX_ERR_NEW_PORT_ALLOC); + } else { + SET_ERROR_PARAMETER(error, TOX_ERR_NEW_MALLOC); + } + + return NULL; + } + + if (load_savedata_tox + && messenger_load(m, tox_options_get_savedata_data(options), tox_options_get_savedata_length(options)) == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_NEW_LOAD_BAD_FORMAT); + } else if (load_savedata_sk) { + load_secret_key(m->net_crypto, tox_options_get_savedata_data(options)); + SET_ERROR_PARAMETER(error, TOX_ERR_NEW_OK); + } else { + SET_ERROR_PARAMETER(error, TOX_ERR_NEW_OK); + } + + return m; +} + +void tox_kill(Tox *tox) +{ + if (tox == NULL) { + return; + } + + Messenger *m = tox; + kill_groupchats((Group_Chats *)m->conferences_object); + kill_messenger(m); +} + +size_t tox_get_savedata_size(const Tox *tox) +{ + const Messenger *m = tox; + return messenger_size(m); +} + +void tox_get_savedata(const Tox *tox, uint8_t *savedata) +{ + if (savedata) { + const Messenger *m = tox; + messenger_save(m, savedata); + } +} + +bool tox_bootstrap(Tox *tox, const char *address, uint16_t port, const uint8_t *public_key, TOX_ERR_BOOTSTRAP *error) +{ + if (!address || !public_key) { + SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_NULL); + return 0; + } + + if (port == 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_BAD_PORT); + return 0; + } + + IP_Port *root; + + int32_t count = net_getipport(address, &root, TOX_SOCK_DGRAM); + + if (count == -1) { + net_freeipport(root); + SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_BAD_HOST); + return 0; + } + + unsigned int i; + + for (i = 0; i < count; i++) { + root[i].port = net_htons(port); + + Messenger *m = tox; + onion_add_bs_path_node(m->onion_c, root[i], public_key); + DHT_bootstrap(m->dht, root[i], public_key); + } + + net_freeipport(root); + + if (count) { + SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_OK); + return 1; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_BAD_HOST); + return 0; +} + +bool tox_add_tcp_relay(Tox *tox, const char *address, uint16_t port, const uint8_t *public_key, + TOX_ERR_BOOTSTRAP *error) +{ + if (!address || !public_key) { + SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_NULL); + return 0; + } + + if (port == 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_BAD_PORT); + return 0; + } + + IP_Port *root; + + int32_t count = net_getipport(address, &root, TOX_SOCK_STREAM); + + if (count == -1) { + net_freeipport(root); + SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_BAD_HOST); + return 0; + } + + unsigned int i; + + for (i = 0; i < count; i++) { + root[i].port = net_htons(port); + + Messenger *m = tox; + add_tcp_relay(m->net_crypto, root[i], public_key); + } + + net_freeipport(root); + + if (count) { + SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_OK); + return 1; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_BAD_HOST); + return 0; +} + +TOX_CONNECTION tox_self_get_connection_status(const Tox *tox) +{ + const Messenger *m = tox; + + unsigned int ret = onion_connection_status(m->onion_c); + + if (ret == 2) { + return TOX_CONNECTION_UDP; + } + + if (ret == 1) { + return TOX_CONNECTION_TCP; + } + + return TOX_CONNECTION_NONE; +} + + +void tox_callback_self_connection_status(Tox *tox, tox_self_connection_status_cb *callback) +{ + Messenger *m = tox; + m_callback_core_connection(m, (void (*)(Messenger *, unsigned int, void *))callback); +} + +uint32_t tox_iteration_interval(const Tox *tox) +{ + const Messenger *m = tox; + return messenger_run_interval(m); +} + +void tox_iterate(Tox *tox, void *user_data) +{ + Messenger *m = tox; + do_messenger(m, user_data); + do_groupchats((Group_Chats *)m->conferences_object, user_data); +} + +void tox_self_get_address(const Tox *tox, uint8_t *address) +{ + if (address) { + const Messenger *m = tox; + getaddress(m, address); + } +} + +void tox_self_set_nospam(Tox *tox, uint32_t nospam) +{ + Messenger *m = tox; + set_nospam(&(m->fr), net_htonl(nospam)); +} + +uint32_t tox_self_get_nospam(const Tox *tox) +{ + const Messenger *m = tox; + return net_ntohl(get_nospam(&(m->fr))); +} + +void tox_self_get_public_key(const Tox *tox, uint8_t *public_key) +{ + const Messenger *m = tox; + + if (public_key) { + memcpy(public_key, m->net_crypto->self_public_key, CRYPTO_PUBLIC_KEY_SIZE); + } +} + +void tox_self_get_secret_key(const Tox *tox, uint8_t *secret_key) +{ + const Messenger *m = tox; + + if (secret_key) { + memcpy(secret_key, m->net_crypto->self_secret_key, CRYPTO_SECRET_KEY_SIZE); + } +} + +bool tox_self_set_name(Tox *tox, const uint8_t *name, size_t length, TOX_ERR_SET_INFO *error) +{ + if (!name && length != 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_SET_INFO_NULL); + return 0; + } + + Messenger *m = tox; + + if (setname(m, name, length) == 0) { + // TODO(irungentoo): function to set different per group names? + send_name_all_groups((Group_Chats *)m->conferences_object); + SET_ERROR_PARAMETER(error, TOX_ERR_SET_INFO_OK); + return 1; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_SET_INFO_TOO_LONG); + return 0; +} + +size_t tox_self_get_name_size(const Tox *tox) +{ + const Messenger *m = tox; + return m_get_self_name_size(m); +} + +void tox_self_get_name(const Tox *tox, uint8_t *name) +{ + if (name) { + const Messenger *m = tox; + getself_name(m, name); + } +} + +bool tox_self_set_status_message(Tox *tox, const uint8_t *status_message, size_t length, TOX_ERR_SET_INFO *error) +{ + if (!status_message && length != 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_SET_INFO_NULL); + return 0; + } + + Messenger *m = tox; + + if (m_set_statusmessage(m, status_message, length) == 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_SET_INFO_OK); + return 1; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_SET_INFO_TOO_LONG); + return 0; +} + +size_t tox_self_get_status_message_size(const Tox *tox) +{ + const Messenger *m = tox; + return m_get_self_statusmessage_size(m); +} + +void tox_self_get_status_message(const Tox *tox, uint8_t *status_message) +{ + if (status_message) { + const Messenger *m = tox; + m_copy_self_statusmessage(m, status_message); + } +} + +void tox_self_set_status(Tox *tox, TOX_USER_STATUS status) +{ + Messenger *m = tox; + m_set_userstatus(m, status); +} + +TOX_USER_STATUS tox_self_get_status(const Tox *tox) +{ + const Messenger *m = tox; + return (TOX_USER_STATUS)m_get_self_userstatus(m); +} + +static void set_friend_error(int32_t ret, TOX_ERR_FRIEND_ADD *error) +{ + switch (ret) { + case FAERR_TOOLONG: + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_TOO_LONG); + break; + + case FAERR_NOMESSAGE: + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_NO_MESSAGE); + break; + + case FAERR_OWNKEY: + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_OWN_KEY); + break; + + case FAERR_ALREADYSENT: + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_ALREADY_SENT); + break; + + case FAERR_BADCHECKSUM: + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_BAD_CHECKSUM); + break; + + case FAERR_SETNEWNOSPAM: + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_SET_NEW_NOSPAM); + break; + + case FAERR_NOMEM: + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_MALLOC); + break; + } +} + +uint32_t tox_friend_add(Tox *tox, const uint8_t *address, const uint8_t *message, size_t length, + TOX_ERR_FRIEND_ADD *error) +{ + if (!address || !message) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_NULL); + return UINT32_MAX; + } + + Messenger *m = tox; + int32_t ret = m_addfriend(m, address, message, length); + + if (ret >= 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_OK); + return ret; + } + + set_friend_error(ret, error); + return UINT32_MAX; +} + +uint32_t tox_friend_add_norequest(Tox *tox, const uint8_t *public_key, TOX_ERR_FRIEND_ADD *error) +{ + if (!public_key) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_NULL); + return UINT32_MAX; + } + + Messenger *m = tox; + int32_t ret = m_addfriend_norequest(m, public_key); + + if (ret >= 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_OK); + return ret; + } + + set_friend_error(ret, error); + return UINT32_MAX; +} + +bool tox_friend_delete(Tox *tox, uint32_t friend_number, TOX_ERR_FRIEND_DELETE *error) +{ + Messenger *m = tox; + int ret = m_delfriend(m, friend_number); + + // TODO(irungentoo): handle if realloc fails? + if (ret == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_DELETE_FRIEND_NOT_FOUND); + return 0; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_DELETE_OK); + return 1; +} + +uint32_t tox_friend_by_public_key(const Tox *tox, const uint8_t *public_key, TOX_ERR_FRIEND_BY_PUBLIC_KEY *error) +{ + if (!public_key) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_BY_PUBLIC_KEY_NULL); + return UINT32_MAX; + } + + const Messenger *m = tox; + int32_t ret = getfriend_id(m, public_key); + + if (ret == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_BY_PUBLIC_KEY_NOT_FOUND); + return UINT32_MAX; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_BY_PUBLIC_KEY_OK); + return ret; +} + +bool tox_friend_get_public_key(const Tox *tox, uint32_t friend_number, uint8_t *public_key, + TOX_ERR_FRIEND_GET_PUBLIC_KEY *error) +{ + if (!public_key) { + return 0; + } + + const Messenger *m = tox; + + if (get_real_pk(m, friend_number, public_key) == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_GET_PUBLIC_KEY_FRIEND_NOT_FOUND); + return 0; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_GET_PUBLIC_KEY_OK); + return 1; +} + +bool tox_friend_exists(const Tox *tox, uint32_t friend_number) +{ + const Messenger *m = tox; + return m_friend_exists(m, friend_number); +} + +uint64_t tox_friend_get_last_online(const Tox *tox, uint32_t friend_number, TOX_ERR_FRIEND_GET_LAST_ONLINE *error) +{ + const Messenger *m = tox; + uint64_t timestamp = m_get_last_online(m, friend_number); + + if (timestamp == UINT64_MAX) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_GET_LAST_ONLINE_FRIEND_NOT_FOUND) + return UINT64_MAX; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_GET_LAST_ONLINE_OK); + return timestamp; +} + +size_t tox_self_get_friend_list_size(const Tox *tox) +{ + const Messenger *m = tox; + return count_friendlist(m); +} + +void tox_self_get_friend_list(const Tox *tox, uint32_t *friend_list) +{ + if (friend_list) { + const Messenger *m = tox; + // TODO(irungentoo): size parameter? + copy_friendlist(m, friend_list, tox_self_get_friend_list_size(tox)); + } +} + +size_t tox_friend_get_name_size(const Tox *tox, uint32_t friend_number, TOX_ERR_FRIEND_QUERY *error) +{ + const Messenger *m = tox; + int ret = m_get_name_size(m, friend_number); + + if (ret == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND); + return SIZE_MAX; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_OK); + return ret; +} + +bool tox_friend_get_name(const Tox *tox, uint32_t friend_number, uint8_t *name, TOX_ERR_FRIEND_QUERY *error) +{ + if (!name) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_NULL); + return 0; + } + + const Messenger *m = tox; + int ret = getname(m, friend_number, name); + + if (ret == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND); + return 0; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_OK); + return 1; +} + +void tox_callback_friend_name(Tox *tox, tox_friend_name_cb *callback) +{ + Messenger *m = tox; + m_callback_namechange(m, callback); +} + +size_t tox_friend_get_status_message_size(const Tox *tox, uint32_t friend_number, TOX_ERR_FRIEND_QUERY *error) +{ + const Messenger *m = tox; + int ret = m_get_statusmessage_size(m, friend_number); + + if (ret == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND); + return SIZE_MAX; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_OK); + return ret; +} + +bool tox_friend_get_status_message(const Tox *tox, uint32_t friend_number, uint8_t *status_message, + TOX_ERR_FRIEND_QUERY *error) +{ + if (!status_message) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_NULL); + return 0; + } + + const Messenger *m = tox; + // TODO(irungentoo): size parameter? + int ret = m_copy_statusmessage(m, friend_number, status_message, m_get_statusmessage_size(m, friend_number)); + + if (ret == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND); + return 0; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_OK); + return 1; +} + +void tox_callback_friend_status_message(Tox *tox, tox_friend_status_message_cb *callback) +{ + Messenger *m = tox; + m_callback_statusmessage(m, callback); +} + +TOX_USER_STATUS tox_friend_get_status(const Tox *tox, uint32_t friend_number, TOX_ERR_FRIEND_QUERY *error) +{ + const Messenger *m = tox; + + int ret = m_get_userstatus(m, friend_number); + + if (ret == USERSTATUS_INVALID) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND); + return (TOX_USER_STATUS)(TOX_USER_STATUS_BUSY + 1); + } + + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_OK); + return (TOX_USER_STATUS)ret; +} + +void tox_callback_friend_status(Tox *tox, tox_friend_status_cb *callback) +{ + Messenger *m = tox; + m_callback_userstatus(m, (void (*)(Messenger *, uint32_t, unsigned int, void *))callback); +} + +TOX_CONNECTION tox_friend_get_connection_status(const Tox *tox, uint32_t friend_number, TOX_ERR_FRIEND_QUERY *error) +{ + const Messenger *m = tox; + + int ret = m_get_friend_connectionstatus(m, friend_number); + + if (ret == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND); + return TOX_CONNECTION_NONE; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_OK); + return (TOX_CONNECTION)ret; +} + +void tox_callback_friend_connection_status(Tox *tox, tox_friend_connection_status_cb *callback) +{ + Messenger *m = tox; + m_callback_connectionstatus(m, (void (*)(Messenger *, uint32_t, unsigned int, void *))callback); +} + +bool tox_friend_get_typing(const Tox *tox, uint32_t friend_number, TOX_ERR_FRIEND_QUERY *error) +{ + const Messenger *m = tox; + int ret = m_get_istyping(m, friend_number); + + if (ret == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND); + return 0; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_OK); + return !!ret; +} + +void tox_callback_friend_typing(Tox *tox, tox_friend_typing_cb *callback) +{ + Messenger *m = tox; + m_callback_typingchange(m, callback); +} + +bool tox_self_set_typing(Tox *tox, uint32_t friend_number, bool typing, TOX_ERR_SET_TYPING *error) +{ + Messenger *m = tox; + + if (m_set_usertyping(m, friend_number, typing) == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_SET_TYPING_FRIEND_NOT_FOUND); + return 0; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_SET_TYPING_OK); + return 1; +} + +static void set_message_error(int ret, TOX_ERR_FRIEND_SEND_MESSAGE *error) +{ + switch (ret) { + case 0: + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_SEND_MESSAGE_OK); + break; + + case -1: + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_SEND_MESSAGE_FRIEND_NOT_FOUND); + break; + + case -2: + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_SEND_MESSAGE_TOO_LONG); + break; + + case -3: + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_SEND_MESSAGE_FRIEND_NOT_CONNECTED); + break; + + case -4: + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_SEND_MESSAGE_SENDQ); + break; + + case -5: + /* can't happen */ + break; + } +} + +uint32_t tox_friend_send_message(Tox *tox, uint32_t friend_number, TOX_MESSAGE_TYPE type, const uint8_t *message, + size_t length, TOX_ERR_FRIEND_SEND_MESSAGE *error) +{ + if (!message) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_SEND_MESSAGE_NULL); + return 0; + } + + if (!length) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_SEND_MESSAGE_EMPTY); + return 0; + } + + Messenger *m = tox; + uint32_t message_id = 0; + set_message_error(m_send_message_generic(m, friend_number, type, message, length, &message_id), error); + return message_id; +} + +void tox_callback_friend_read_receipt(Tox *tox, tox_friend_read_receipt_cb *callback) +{ + Messenger *m = tox; + m_callback_read_receipt(m, callback); +} + +void tox_callback_friend_request(Tox *tox, tox_friend_request_cb *callback) +{ + Messenger *m = tox; + m_callback_friendrequest(m, callback); +} + +void tox_callback_friend_message(Tox *tox, tox_friend_message_cb *callback) +{ + Messenger *m = tox; + m_callback_friendmessage(m, (void (*)(Messenger *, uint32_t, unsigned int, const uint8_t *, size_t, void *))callback); +} + +bool tox_hash(uint8_t *hash, const uint8_t *data, size_t length) +{ + if (!hash || (length && !data)) { + return 0; + } + + crypto_sha256(hash, data, length); + return 1; +} + +bool tox_file_control(Tox *tox, uint32_t friend_number, uint32_t file_number, TOX_FILE_CONTROL control, + TOX_ERR_FILE_CONTROL *error) +{ + Messenger *m = tox; + int ret = file_control(m, friend_number, file_number, control); + + if (ret == 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_CONTROL_OK); + return 1; + } + + switch (ret) { + case -1: + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_CONTROL_FRIEND_NOT_FOUND); + return 0; + + case -2: + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_CONTROL_FRIEND_NOT_CONNECTED); + return 0; + + case -3: + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_CONTROL_NOT_FOUND); + return 0; + + case -4: + /* can't happen */ + return 0; + + case -5: + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_CONTROL_ALREADY_PAUSED); + return 0; + + case -6: + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_CONTROL_DENIED); + return 0; + + case -7: + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_CONTROL_NOT_PAUSED); + return 0; + + case -8: + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_CONTROL_SENDQ); + return 0; + } + + /* can't happen */ + return 0; +} + +bool tox_file_seek(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, + TOX_ERR_FILE_SEEK *error) +{ + Messenger *m = tox; + int ret = file_seek(m, friend_number, file_number, position); + + if (ret == 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEEK_OK); + return 1; + } + + switch (ret) { + case -1: + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEEK_FRIEND_NOT_FOUND); + return 0; + + case -2: + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEEK_FRIEND_NOT_CONNECTED); + return 0; + + case -3: + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEEK_NOT_FOUND); + return 0; + + case -4: // fall-through + case -5: + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEEK_DENIED); + return 0; + + case -6: + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEEK_INVALID_POSITION); + return 0; + + case -8: + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEEK_SENDQ); + return 0; + } + + /* can't happen */ + return 0; +} + +void tox_callback_file_recv_control(Tox *tox, tox_file_recv_control_cb *callback) +{ + Messenger *m = tox; + callback_file_control(m, (void (*)(Messenger *, uint32_t, uint32_t, unsigned int, void *))callback); +} + +bool tox_file_get_file_id(const Tox *tox, uint32_t friend_number, uint32_t file_number, uint8_t *file_id, + TOX_ERR_FILE_GET *error) +{ + if (!file_id) { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_GET_NULL); + return 0; + } + + const Messenger *m = tox; + int ret = file_get_id(m, friend_number, file_number, file_id); + + if (ret == 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_GET_OK); + return 1; + } + + if (ret == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_GET_FRIEND_NOT_FOUND); + } else { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_GET_NOT_FOUND); + } + + return 0; +} + +uint32_t tox_file_send(Tox *tox, uint32_t friend_number, uint32_t kind, uint64_t file_size, const uint8_t *file_id, + const uint8_t *filename, size_t filename_length, TOX_ERR_FILE_SEND *error) +{ + if (filename_length && !filename) { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_NULL); + return UINT32_MAX; + } + + uint8_t f_id[FILE_ID_LENGTH]; + + if (!file_id) { + /* Tox keys are 32 bytes like FILE_ID_LENGTH. */ + new_symmetric_key(f_id); + file_id = f_id; + } + + Messenger *m = tox; + long int file_num = new_filesender(m, friend_number, kind, file_size, file_id, filename, filename_length); + + if (file_num >= 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_OK); + return file_num; + } + + switch (file_num) { + case -1: + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_FRIEND_NOT_FOUND); + return UINT32_MAX; + + case -2: + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_NAME_TOO_LONG); + return UINT32_MAX; + + case -3: + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_TOO_MANY); + return UINT32_MAX; + + case -4: + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_FRIEND_NOT_CONNECTED); + return UINT32_MAX; + } + + /* can't happen */ + return UINT32_MAX; +} + +bool tox_file_send_chunk(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, const uint8_t *data, + size_t length, TOX_ERR_FILE_SEND_CHUNK *error) +{ + Messenger *m = tox; + int ret = file_data(m, friend_number, file_number, position, data, length); + + if (ret == 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_CHUNK_OK); + return 1; + } + + switch (ret) { + case -1: + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_CHUNK_FRIEND_NOT_FOUND); + return 0; + + case -2: + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_CHUNK_FRIEND_NOT_CONNECTED); + return 0; + + case -3: + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_CHUNK_NOT_FOUND); + return 0; + + case -4: + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_CHUNK_NOT_TRANSFERRING); + return 0; + + case -5: + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_CHUNK_INVALID_LENGTH); + return 0; + + case -6: + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_CHUNK_SENDQ); + return 0; + + case -7: + SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_CHUNK_WRONG_POSITION); + return 0; + } + + /* can't happen */ + return 0; +} + +void tox_callback_file_chunk_request(Tox *tox, tox_file_chunk_request_cb *callback) +{ + Messenger *m = tox; + callback_file_reqchunk(m, callback); +} + +void tox_callback_file_recv(Tox *tox, tox_file_recv_cb *callback) +{ + Messenger *m = tox; + callback_file_sendrequest(m, callback); +} + +void tox_callback_file_recv_chunk(Tox *tox, tox_file_recv_chunk_cb *callback) +{ + Messenger *m = tox; + callback_file_data(m, callback); +} + +void tox_callback_conference_invite(Tox *tox, tox_conference_invite_cb *callback) +{ + Messenger *m = tox; + g_callback_group_invite((Group_Chats *)m->conferences_object, (void (*)(Messenger * m, uint32_t, int, const uint8_t *, + size_t, + void *))callback); +} + +void tox_callback_conference_message(Tox *tox, tox_conference_message_cb *callback) +{ + Messenger *m = tox; + g_callback_group_message((Group_Chats *)m->conferences_object, (void (*)(Messenger * m, uint32_t, uint32_t, int, + const uint8_t *, + size_t, void *))callback); +} + +void tox_callback_conference_title(Tox *tox, tox_conference_title_cb *callback) +{ + Messenger *m = tox; + g_callback_group_title((Group_Chats *)m->conferences_object, callback); +} + +void tox_callback_conference_namelist_change(Tox *tox, tox_conference_namelist_change_cb *callback) +{ + Messenger *m = tox; + g_callback_group_namelistchange((Group_Chats *)m->conferences_object, (void (*)(struct Messenger *, int, int, uint8_t, + void *))callback); +} + +uint32_t tox_conference_new(Tox *tox, TOX_ERR_CONFERENCE_NEW *error) +{ + Messenger *m = tox; + int ret = add_groupchat((Group_Chats *)m->conferences_object, GROUPCHAT_TYPE_TEXT); + + if (ret == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_NEW_INIT); + return UINT32_MAX; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_NEW_OK); + return ret; +} + +bool tox_conference_delete(Tox *tox, uint32_t conference_number, TOX_ERR_CONFERENCE_DELETE *error) +{ + Messenger *m = tox; + int ret = del_groupchat((Group_Chats *)m->conferences_object, conference_number); + + if (ret == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_DELETE_CONFERENCE_NOT_FOUND); + return false; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_DELETE_OK); + return true; +} + +uint32_t tox_conference_peer_count(const Tox *tox, uint32_t conference_number, TOX_ERR_CONFERENCE_PEER_QUERY *error) +{ + const Messenger *m = tox; + int ret = group_number_peers((Group_Chats *)m->conferences_object, conference_number); + + if (ret == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_CONFERENCE_NOT_FOUND); + return UINT32_MAX; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_OK); + return ret; +} + +size_t tox_conference_peer_get_name_size(const Tox *tox, uint32_t conference_number, uint32_t peer_number, + TOX_ERR_CONFERENCE_PEER_QUERY *error) +{ + const Messenger *m = tox; + int ret = group_peername_size((Group_Chats *)m->conferences_object, conference_number, peer_number); + + switch (ret) { + case -1: + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_CONFERENCE_NOT_FOUND); + return -1; + + case -2: + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_PEER_NOT_FOUND); + return -1; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_OK); + return ret; +} + +bool tox_conference_peer_get_name(const Tox *tox, uint32_t conference_number, uint32_t peer_number, uint8_t *name, + TOX_ERR_CONFERENCE_PEER_QUERY *error) +{ + const Messenger *m = tox; + int ret = group_peername((Group_Chats *)m->conferences_object, conference_number, peer_number, name); + + switch (ret) { + case -1: + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_CONFERENCE_NOT_FOUND); + return false; + + case -2: + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_PEER_NOT_FOUND); + return false; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_OK); + return true; +} + +bool tox_conference_peer_get_public_key(const Tox *tox, uint32_t conference_number, uint32_t peer_number, + uint8_t *public_key, TOX_ERR_CONFERENCE_PEER_QUERY *error) +{ + const Messenger *m = tox; + int ret = group_peer_pubkey((Group_Chats *)m->conferences_object, conference_number, peer_number, public_key); + + switch (ret) { + case -1: + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_CONFERENCE_NOT_FOUND); + return false; + + case -2: + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_PEER_NOT_FOUND); + return false; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_OK); + return true; +} + +bool tox_conference_peer_number_is_ours(const Tox *tox, uint32_t conference_number, uint32_t peer_number, + TOX_ERR_CONFERENCE_PEER_QUERY *error) +{ + const Messenger *m = tox; + int ret = group_peernumber_is_ours((Group_Chats *)m->conferences_object, conference_number, peer_number); + + switch (ret) { + case -1: + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_CONFERENCE_NOT_FOUND); + return false; + + case -2: + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_PEER_NOT_FOUND); + return false; + + case -3: + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_NO_CONNECTION); + return false; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_OK); + return ret; +} + +bool tox_conference_invite(Tox *tox, uint32_t friend_number, uint32_t conference_number, + TOX_ERR_CONFERENCE_INVITE *error) +{ + Messenger *m = tox; + int ret = invite_friend((Group_Chats *)m->conferences_object, friend_number, conference_number); + + switch (ret) { + case -1: + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_INVITE_CONFERENCE_NOT_FOUND); + return false; + + case -2: + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_INVITE_FAIL_SEND); + return false; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_INVITE_OK); + return true; +} + +uint32_t tox_conference_join(Tox *tox, uint32_t friend_number, const uint8_t *cookie, size_t length, + TOX_ERR_CONFERENCE_JOIN *error) +{ + Messenger *m = tox; + int ret = join_groupchat((Group_Chats *)m->conferences_object, friend_number, GROUPCHAT_TYPE_TEXT, cookie, length); + + switch (ret) { + case -1: + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_JOIN_INVALID_LENGTH); + return UINT32_MAX; + + case -2: + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_JOIN_WRONG_TYPE); + return UINT32_MAX; + + case -3: + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_JOIN_FRIEND_NOT_FOUND); + return UINT32_MAX; + + case -4: + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_JOIN_DUPLICATE); + return UINT32_MAX; + + case -5: + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_JOIN_INIT_FAIL); + return UINT32_MAX; + + case -6: + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_JOIN_FAIL_SEND); + return UINT32_MAX; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_JOIN_OK); + return ret; +} + +bool tox_conference_send_message(Tox *tox, uint32_t conference_number, TOX_MESSAGE_TYPE type, const uint8_t *message, + size_t length, TOX_ERR_CONFERENCE_SEND_MESSAGE *error) +{ + Messenger *m = tox; + int ret = 0; + + if (type == TOX_MESSAGE_TYPE_NORMAL) { + ret = group_message_send((Group_Chats *)m->conferences_object, conference_number, message, length); + } else { + ret = group_action_send((Group_Chats *)m->conferences_object, conference_number, message, length); + } + + switch (ret) { + case -1: + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_SEND_MESSAGE_CONFERENCE_NOT_FOUND); + return false; + + case -2: + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_SEND_MESSAGE_TOO_LONG); + return false; + + case -3: + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_SEND_MESSAGE_NO_CONNECTION); + return false; + + case -4: + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_SEND_MESSAGE_FAIL_SEND); + return false; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_SEND_MESSAGE_OK); + return true; +} + +size_t tox_conference_get_title_size(const Tox *tox, uint32_t conference_number, TOX_ERR_CONFERENCE_TITLE *error) +{ + const Messenger *m = tox; + int ret = group_title_get_size((Group_Chats *)m->conferences_object, conference_number); + + switch (ret) { + case -1: + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_TITLE_CONFERENCE_NOT_FOUND); + return -1; + + case -2: + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_TITLE_INVALID_LENGTH); + return -1; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_TITLE_OK); + return ret; +} + +bool tox_conference_get_title(const Tox *tox, uint32_t conference_number, uint8_t *title, + TOX_ERR_CONFERENCE_TITLE *error) +{ + const Messenger *m = tox; + int ret = group_title_get((Group_Chats *)m->conferences_object, conference_number, title); + + switch (ret) { + case -1: + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_TITLE_CONFERENCE_NOT_FOUND); + return false; + + case -2: + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_TITLE_INVALID_LENGTH); + return false; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_TITLE_OK); + return true; +} + +bool tox_conference_set_title(Tox *tox, uint32_t conference_number, const uint8_t *title, size_t length, + TOX_ERR_CONFERENCE_TITLE *error) +{ + Messenger *m = tox; + int ret = group_title_send((Group_Chats *)m->conferences_object, conference_number, title, length); + + switch (ret) { + case -1: + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_TITLE_CONFERENCE_NOT_FOUND); + return false; + + case -2: + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_TITLE_INVALID_LENGTH); + return false; + + case -3: + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_TITLE_FAIL_SEND); + return false; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_TITLE_OK); + return true; +} + +size_t tox_conference_get_chatlist_size(const Tox *tox) +{ + const Messenger *m = tox; + return count_chatlist((Group_Chats *)m->conferences_object); +} + +void tox_conference_get_chatlist(const Tox *tox, uint32_t *chatlist) +{ + const Messenger *m = tox; + size_t list_size = tox_conference_get_chatlist_size(tox); + copy_chatlist((Group_Chats *)m->conferences_object, chatlist, list_size); +} + +TOX_CONFERENCE_TYPE tox_conference_get_type(const Tox *tox, uint32_t conference_number, + TOX_ERR_CONFERENCE_GET_TYPE *error) +{ + const Messenger *m = tox; + int ret = group_get_type((Group_Chats *)m->conferences_object, conference_number); + + if (ret == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_GET_TYPE_CONFERENCE_NOT_FOUND); + return (TOX_CONFERENCE_TYPE)ret; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_GET_TYPE_OK); + return (TOX_CONFERENCE_TYPE)ret; +} + +static void set_custom_packet_error(int ret, TOX_ERR_FRIEND_CUSTOM_PACKET *error) +{ + switch (ret) { + case 0: + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_OK); + break; + + case -1: + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_FRIEND_NOT_FOUND); + break; + + case -2: + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_TOO_LONG); + break; + + case -3: + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_INVALID); + break; + + case -4: + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_FRIEND_NOT_CONNECTED); + break; + + case -5: + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_SENDQ); + break; + } +} + +bool tox_friend_send_lossy_packet(Tox *tox, uint32_t friend_number, const uint8_t *data, size_t length, + TOX_ERR_FRIEND_CUSTOM_PACKET *error) +{ + if (!data) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_NULL); + return 0; + } + + Messenger *m = tox; + + if (length == 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_EMPTY); + return 0; + } + + if (data[0] < (PACKET_ID_LOSSY_RANGE_START + PACKET_LOSSY_AV_RESERVED)) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_INVALID); + return 0; + } + + int ret = m_send_custom_lossy_packet(m, friend_number, data, length); + + set_custom_packet_error(ret, error); + + if (ret == 0) { + return 1; + } + + return 0; +} + +void tox_callback_friend_lossy_packet(Tox *tox, tox_friend_lossy_packet_cb *callback) +{ + Messenger *m = tox; + custom_lossy_packet_registerhandler(m, callback); +} + +bool tox_friend_send_lossless_packet(Tox *tox, uint32_t friend_number, const uint8_t *data, size_t length, + TOX_ERR_FRIEND_CUSTOM_PACKET *error) +{ + if (!data) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_NULL); + return 0; + } + + Messenger *m = tox; + + if (length == 0) { + SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_EMPTY); + return 0; + } + + int ret = send_custom_lossless_packet(m, friend_number, data, length); + + set_custom_packet_error(ret, error); + + if (ret == 0) { + return 1; + } + + return 0; +} + +void tox_callback_friend_lossless_packet(Tox *tox, tox_friend_lossless_packet_cb *callback) +{ + Messenger *m = tox; + custom_lossless_packet_registerhandler(m, callback); +} + +void tox_self_get_dht_id(const Tox *tox, uint8_t *dht_id) +{ + if (dht_id) { + const Messenger *m = tox; + memcpy(dht_id, m->dht->self_public_key, CRYPTO_PUBLIC_KEY_SIZE); + } +} + +uint16_t tox_self_get_udp_port(const Tox *tox, TOX_ERR_GET_PORT *error) +{ + const Messenger *m = tox; + uint16_t port = net_htons(m->net->port); + + if (port) { + SET_ERROR_PARAMETER(error, TOX_ERR_GET_PORT_OK); + } else { + SET_ERROR_PARAMETER(error, TOX_ERR_GET_PORT_NOT_BOUND); + } + + return port; +} + +uint16_t tox_self_get_tcp_port(const Tox *tox, TOX_ERR_GET_PORT *error) +{ + const Messenger *m = tox; + + if (m->tcp_server) { + SET_ERROR_PARAMETER(error, TOX_ERR_GET_PORT_OK); + return m->options.tcp_server_port; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_GET_PORT_NOT_BOUND); + return 0; +} diff --git a/libs/libtox/src/toxcore/tox.h b/libs/libtox/src/toxcore/tox.h new file mode 100644 index 0000000000..30bc950964 --- /dev/null +++ b/libs/libtox/src/toxcore/tox.h @@ -0,0 +1,2947 @@ +/* + * The Tox public API. + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2013 Tox project. + * + * This file is part of Tox, the free peer to peer instant messenger. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifndef TOX_H +#define TOX_H + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + + +/******************************************************************************* + * `tox.h` SHOULD *NOT* BE EDITED MANUALLY – any changes should be made to * + * `tox.api.h`, located in `toxcore/`. For instructions on how to * + * generate `tox.h` from `tox.api.h` please refer to `docs/apidsl.md` * + ******************************************************************************/ + + + +/** \page core Public core API for Tox clients. + * + * Every function that can fail takes a function-specific error code pointer + * that can be used to diagnose problems with the Tox state or the function + * arguments. The error code pointer can be NULL, which does not influence the + * function's behaviour, but can be done if the reason for failure is irrelevant + * to the client. + * + * The exception to this rule are simple allocation functions whose only failure + * mode is allocation failure. They return NULL in that case, and do not set an + * error code. + * + * Every error code type has an OK value to which functions will set their error + * code value on success. Clients can keep their error code uninitialised before + * passing it to a function. The library guarantees that after returning, the + * value pointed to by the error code pointer has been initialised. + * + * Functions with pointer parameters often have a NULL error code, meaning they + * could not perform any operation, because one of the required parameters was + * NULL. Some functions operate correctly or are defined as effectless on NULL. + * + * Some functions additionally return a value outside their + * return type domain, or a bool containing true on success and false on + * failure. + * + * All functions that take a Tox instance pointer will cause undefined behaviour + * when passed a NULL Tox pointer. + * + * All integer values are expected in host byte order. + * + * Functions with parameters with enum types cause unspecified behaviour if the + * enumeration value is outside the valid range of the type. If possible, the + * function will try to use a sane default, but there will be no error code, + * and one possible action for the function to take is to have no effect. + * + * Integer constants and the memory layout of publicly exposed structs are not + * part of the ABI. + */ +/** \subsection events Events and callbacks + * + * Events are handled by callbacks. One callback can be registered per event. + * All events have a callback function type named `tox_{event}_cb` and a + * function to register it named `tox_callback_{event}`. Passing a NULL + * callback will result in no callback being registered for that event. Only + * one callback per event can be registered, so if a client needs multiple + * event listeners, it needs to implement the dispatch functionality itself. + * + * The last argument to a callback is the user data pointer. It is passed from + * tox_iterate to each callback in sequence. + * + * The user data pointer is never stored or dereferenced by any library code, so + * can be any pointer, including NULL. Callbacks must all operate on the same + * object type. In the apidsl code (tox.in.h), this is denoted with `any`. The + * `any` in tox_iterate must be the same `any` as in all callbacks. In C, + * lacking parametric polymorphism, this is a pointer to void. + * + * Old style callbacks that are registered together with a user data pointer + * receive that pointer as argument when they are called. They can each have + * their own user data pointer of their own type. + */ +/** \subsection threading Threading implications + * + * It is possible to run multiple concurrent threads with a Tox instance for + * each thread. It is also possible to run all Tox instances in the same thread. + * A common way to run Tox (multiple or single instance) is to have one thread + * running a simple tox_iterate loop, sleeping for tox_iteration_interval + * milliseconds on each iteration. + * + * If you want to access a single Tox instance from multiple threads, access + * to the instance must be synchronised. While multiple threads can concurrently + * access multiple different Tox instances, no more than one API function can + * operate on a single instance at any given time. + * + * Functions that write to variable length byte arrays will always have a size + * function associated with them. The result of this size function is only valid + * until another mutating function (one that takes a pointer to non-const Tox) + * is called. Thus, clients must ensure that no other thread calls a mutating + * function between the call to the size function and the call to the retrieval + * function. + * + * E.g. to get the current nickname, one would write + * + * \code + * size_t length = tox_self_get_name_size(tox); + * uint8_t *name = malloc(length); + * if (!name) abort(); + * tox_self_get_name(tox, name); + * \endcode + * + * If any other thread calls tox_self_set_name while this thread is allocating + * memory, the length may have become invalid, and the call to + * tox_self_get_name may cause undefined behaviour. + */ +/** + * The Tox instance type. All the state associated with a connection is held + * within the instance. Multiple instances can exist and operate concurrently. + * The maximum number of Tox instances that can exist on a single network + * device is limited. Note that this is not just a per-process limit, since the + * limiting factor is the number of usable ports on a device. + */ +#ifndef TOX_DEFINED +#define TOX_DEFINED +typedef struct Tox Tox; +#endif /* TOX_DEFINED */ + + +/******************************************************************************* + * + * :: API version + * + ******************************************************************************/ + + + +/** + * The major version number. Incremented when the API or ABI changes in an + * incompatible way. + * + * The function variants of these constants return the version number of the + * library. They can be used to display the Tox library version or to check + * whether the client is compatible with the dynamically linked version of Tox. + */ +#define TOX_VERSION_MAJOR 0 + +uint32_t tox_version_major(void); + +/** + * The minor version number. Incremented when functionality is added without + * breaking the API or ABI. Set to 0 when the major version number is + * incremented. + */ +#define TOX_VERSION_MINOR 1 + +uint32_t tox_version_minor(void); + +/** + * The patch or revision number. Incremented when bugfixes are applied without + * changing any functionality or API or ABI. + */ +#define TOX_VERSION_PATCH 10 + +uint32_t tox_version_patch(void); + +/** + * A macro to check at preprocessing time whether the client code is compatible + * with the installed version of Tox. Leading zeros in the version number are + * ignored. E.g. 0.1.5 is to 0.1.4 what 1.5 is to 1.4, that is: it can add new + * features, but can't break the API. + */ +#define TOX_VERSION_IS_API_COMPATIBLE(MAJOR, MINOR, PATCH) \ + (TOX_VERSION_MAJOR > 0 && TOX_VERSION_MAJOR == MAJOR) && ( \ + /* 1.x.x, 2.x.x, etc. with matching major version. */ \ + TOX_VERSION_MINOR > MINOR || \ + TOX_VERSION_MINOR == MINOR && TOX_VERSION_PATCH >= PATCH \ + ) || (TOX_VERSION_MAJOR == 0 && MAJOR == 0) && ( \ + /* 0.x.x makes minor behave like major above. */ \ + (TOX_VERSION_MINOR > 0 && TOX_VERSION_MINOR == MINOR) && ( \ + TOX_VERSION_PATCH >= PATCH \ + ) || (TOX_VERSION_MINOR == 0 && MINOR == 0) && ( \ + /* 0.0.x and 0.0.y are only compatible if x == y. */ \ + TOX_VERSION_PATCH == PATCH \ + ) \ + ) + +/** + * Return whether the compiled library version is compatible with the passed + * version numbers. + */ +bool tox_version_is_compatible(uint32_t major, uint32_t minor, uint32_t patch); + +/** + * A convenience macro to call tox_version_is_compatible with the currently + * compiling API version. + */ +#define TOX_VERSION_IS_ABI_COMPATIBLE() \ + tox_version_is_compatible(TOX_VERSION_MAJOR, TOX_VERSION_MINOR, TOX_VERSION_PATCH) + + +/******************************************************************************* + * + * :: Numeric constants + * + * The values of these are not part of the ABI. Prefer to use the function + * versions of them for code that should remain compatible with future versions + * of toxcore. + * + ******************************************************************************/ + + + +/** + * The size of a Tox Public Key in bytes. + */ +#define TOX_PUBLIC_KEY_SIZE 32 + +uint32_t tox_public_key_size(void); + +/** + * The size of a Tox Secret Key in bytes. + */ +#define TOX_SECRET_KEY_SIZE 32 + +uint32_t tox_secret_key_size(void); + +/** + * The size of the nospam in bytes when written in a Tox address. + */ +#define TOX_NOSPAM_SIZE (sizeof(uint32_t)) + +uint32_t tox_nospam_size(void); + +/** + * The size of a Tox address in bytes. Tox addresses are in the format + * [Public Key (TOX_PUBLIC_KEY_SIZE bytes)][nospam (4 bytes)][checksum (2 bytes)]. + * + * The checksum is computed over the Public Key and the nospam value. The first + * byte is an XOR of all the even bytes (0, 2, 4, ...), the second byte is an + * XOR of all the odd bytes (1, 3, 5, ...) of the Public Key and nospam. + */ +#define TOX_ADDRESS_SIZE (TOX_PUBLIC_KEY_SIZE + TOX_NOSPAM_SIZE + sizeof(uint16_t)) + +uint32_t tox_address_size(void); + +/** + * Maximum length of a nickname in bytes. + */ +#define TOX_MAX_NAME_LENGTH 128 + +uint32_t tox_max_name_length(void); + +/** + * Maximum length of a status message in bytes. + */ +#define TOX_MAX_STATUS_MESSAGE_LENGTH 1007 + +uint32_t tox_max_status_message_length(void); + +/** + * Maximum length of a friend request message in bytes. + */ +#define TOX_MAX_FRIEND_REQUEST_LENGTH 1016 + +uint32_t tox_max_friend_request_length(void); + +/** + * Maximum length of a single message after which it should be split. + */ +#define TOX_MAX_MESSAGE_LENGTH 1372 + +uint32_t tox_max_message_length(void); + +/** + * Maximum size of custom packets. TODO(iphydf): should be LENGTH? + */ +#define TOX_MAX_CUSTOM_PACKET_SIZE 1373 + +uint32_t tox_max_custom_packet_size(void); + +/** + * The number of bytes in a hash generated by tox_hash. + */ +#define TOX_HASH_LENGTH 32 + +uint32_t tox_hash_length(void); + +/** + * The number of bytes in a file id. + */ +#define TOX_FILE_ID_LENGTH 32 + +uint32_t tox_file_id_length(void); + +/** + * Maximum file name length for file transfers. + */ +#define TOX_MAX_FILENAME_LENGTH 255 + +uint32_t tox_max_filename_length(void); + + +/******************************************************************************* + * + * :: Global enumerations + * + ******************************************************************************/ + + + +/** + * Represents the possible statuses a client can have. + */ +typedef enum TOX_USER_STATUS { + + /** + * User is online and available. + */ + TOX_USER_STATUS_NONE, + + /** + * User is away. Clients can set this e.g. after a user defined + * inactivity time. + */ + TOX_USER_STATUS_AWAY, + + /** + * User is busy. Signals to other clients that this client does not + * currently wish to communicate. + */ + TOX_USER_STATUS_BUSY, + +} TOX_USER_STATUS; + + +/** + * Represents message types for tox_friend_send_message and conference + * messages. + */ +typedef enum TOX_MESSAGE_TYPE { + + /** + * Normal text message. Similar to PRIVMSG on IRC. + */ + TOX_MESSAGE_TYPE_NORMAL, + + /** + * A message describing an user action. This is similar to /me (CTCP ACTION) + * on IRC. + */ + TOX_MESSAGE_TYPE_ACTION, + +} TOX_MESSAGE_TYPE; + + + +/******************************************************************************* + * + * :: Startup options + * + ******************************************************************************/ + + + +/** + * Type of proxy used to connect to TCP relays. + */ +typedef enum TOX_PROXY_TYPE { + + /** + * Don't use a proxy. + */ + TOX_PROXY_TYPE_NONE, + + /** + * HTTP proxy using CONNECT. + */ + TOX_PROXY_TYPE_HTTP, + + /** + * SOCKS proxy for simple socket pipes. + */ + TOX_PROXY_TYPE_SOCKS5, + +} TOX_PROXY_TYPE; + + +/** + * Type of savedata to create the Tox instance from. + */ +typedef enum TOX_SAVEDATA_TYPE { + + /** + * No savedata. + */ + TOX_SAVEDATA_TYPE_NONE, + + /** + * Savedata is one that was obtained from tox_get_savedata. + */ + TOX_SAVEDATA_TYPE_TOX_SAVE, + + /** + * Savedata is a secret key of length TOX_SECRET_KEY_SIZE. + */ + TOX_SAVEDATA_TYPE_SECRET_KEY, + +} TOX_SAVEDATA_TYPE; + + +/** + * Severity level of log messages. + */ +typedef enum TOX_LOG_LEVEL { + + /** + * Very detailed traces including all network activity. + */ + TOX_LOG_LEVEL_TRACE, + + /** + * Debug messages such as which port we bind to. + */ + TOX_LOG_LEVEL_DEBUG, + + /** + * Informational log messages such as video call status changes. + */ + TOX_LOG_LEVEL_INFO, + + /** + * Warnings about internal inconsistency or logic errors. + */ + TOX_LOG_LEVEL_WARNING, + + /** + * Severe unexpected errors caused by external or internal inconsistency. + */ + TOX_LOG_LEVEL_ERROR, + +} TOX_LOG_LEVEL; + + +/** + * This event is triggered when the toxcore library logs an internal message. + * This is mostly useful for debugging. This callback can be called from any + * function, not just tox_iterate. This means the user data lifetime must at + * least extend between registering and unregistering it or tox_kill. + * + * Other toxcore modules such as toxav may concurrently call this callback at + * any time. Thus, user code must make sure it is equipped to handle concurrent + * execution, e.g. by employing appropriate mutex locking. + * + * @param level The severity of the log message. + * @param file The source file from which the message originated. + * @param line The source line from which the message originated. + * @param func The function from which the message originated. + * @param message The log message. + * @param user_data The user data pointer passed to tox_new in options. + */ +typedef void tox_log_cb(Tox *tox, TOX_LOG_LEVEL level, const char *file, uint32_t line, const char *func, + const char *message, void *user_data); + + +/** + * This struct contains all the startup options for Tox. You must tox_options_new to + * allocate an object of this type. + * + * WARNING: Although this struct happens to be visible in the API, it is + * effectively private. Do not allocate this yourself or access members + * directly, as it *will* break binary compatibility frequently. + * + * @deprecated The memory layout of this struct (size, alignment, and field + * order) is not part of the ABI. To remain compatible, prefer to use tox_options_new to + * allocate the object and accessor functions to set the members. The struct + * will become opaque (i.e. the definition will become private) in v0.2.0. + */ +struct Tox_Options { + + /** + * The type of socket to create. + * + * If this is set to false, an IPv4 socket is created, which subsequently + * only allows IPv4 communication. + * If it is set to true, an IPv6 socket is created, allowing both IPv4 and + * IPv6 communication. + */ + bool ipv6_enabled; + + + /** + * Enable the use of UDP communication when available. + * + * Setting this to false will force Tox to use TCP only. Communications will + * need to be relayed through a TCP relay node, potentially slowing them down. + * Disabling UDP support is necessary when using anonymous proxies or Tor. + */ + bool udp_enabled; + + + /** + * Enable local network peer discovery. + * + * Disabling this will cause Tox to not look for peers on the local network. + */ + bool local_discovery_enabled; + + + /** + * Pass communications through a proxy. + */ + TOX_PROXY_TYPE proxy_type; + + + /** + * The IP address or DNS name of the proxy to be used. + * + * If used, this must be non-NULL and be a valid DNS name. The name must not + * exceed 255 characters, and be in a NUL-terminated C string format + * (255 chars + 1 NUL byte). + * + * This member is ignored (it can be NULL) if proxy_type is TOX_PROXY_TYPE_NONE. + * + * The data pointed at by this member is owned by the user, so must + * outlive the options object. + */ + const char *proxy_host; + + + /** + * The port to use to connect to the proxy server. + * + * Ports must be in the range (1, 65535). The value is ignored if + * proxy_type is TOX_PROXY_TYPE_NONE. + */ + uint16_t proxy_port; + + + /** + * The start port of the inclusive port range to attempt to use. + * + * If both start_port and end_port are 0, the default port range will be + * used: [33445, 33545]. + * + * If either start_port or end_port is 0 while the other is non-zero, the + * non-zero port will be the only port in the range. + * + * Having start_port > end_port will yield the same behavior as if start_port + * and end_port were swapped. + */ + uint16_t start_port; + + + /** + * The end port of the inclusive port range to attempt to use. + */ + uint16_t end_port; + + + /** + * The port to use for the TCP server (relay). If 0, the TCP server is + * disabled. + * + * Enabling it is not required for Tox to function properly. + * + * When enabled, your Tox instance can act as a TCP relay for other Tox + * instance. This leads to increased traffic, thus when writing a client + * it is recommended to enable TCP server only if the user has an option + * to disable it. + */ + uint16_t tcp_port; + + + /** + * Enables or disables UDP hole-punching in toxcore. (Default: enabled). + */ + bool hole_punching_enabled; + + + /** + * The type of savedata to load from. + */ + TOX_SAVEDATA_TYPE savedata_type; + + + /** + * The savedata. + * + * The data pointed at by this member is owned by the user, so must + * outlive the options object. + */ + const uint8_t *savedata_data; + + + /** + * The length of the savedata. + */ + size_t savedata_length; + + + /** + * Logging callback for the new tox instance. + */ + tox_log_cb *log_callback; + + + /** + * User data pointer passed to the logging callback. + */ + void *log_user_data; + +}; + + +bool tox_options_get_ipv6_enabled(const struct Tox_Options *options); + +void tox_options_set_ipv6_enabled(struct Tox_Options *options, bool ipv6_enabled); + +bool tox_options_get_udp_enabled(const struct Tox_Options *options); + +void tox_options_set_udp_enabled(struct Tox_Options *options, bool udp_enabled); + +bool tox_options_get_local_discovery_enabled(const struct Tox_Options *options); + +void tox_options_set_local_discovery_enabled(struct Tox_Options *options, bool local_discovery_enabled); + +TOX_PROXY_TYPE tox_options_get_proxy_type(const struct Tox_Options *options); + +void tox_options_set_proxy_type(struct Tox_Options *options, TOX_PROXY_TYPE type); + +const char *tox_options_get_proxy_host(const struct Tox_Options *options); + +void tox_options_set_proxy_host(struct Tox_Options *options, const char *host); + +uint16_t tox_options_get_proxy_port(const struct Tox_Options *options); + +void tox_options_set_proxy_port(struct Tox_Options *options, uint16_t port); + +uint16_t tox_options_get_start_port(const struct Tox_Options *options); + +void tox_options_set_start_port(struct Tox_Options *options, uint16_t start_port); + +uint16_t tox_options_get_end_port(const struct Tox_Options *options); + +void tox_options_set_end_port(struct Tox_Options *options, uint16_t end_port); + +uint16_t tox_options_get_tcp_port(const struct Tox_Options *options); + +void tox_options_set_tcp_port(struct Tox_Options *options, uint16_t tcp_port); + +bool tox_options_get_hole_punching_enabled(const struct Tox_Options *options); + +void tox_options_set_hole_punching_enabled(struct Tox_Options *options, bool hole_punching_enabled); + +TOX_SAVEDATA_TYPE tox_options_get_savedata_type(const struct Tox_Options *options); + +void tox_options_set_savedata_type(struct Tox_Options *options, TOX_SAVEDATA_TYPE type); + +const uint8_t *tox_options_get_savedata_data(const struct Tox_Options *options); + +void tox_options_set_savedata_data(struct Tox_Options *options, const uint8_t *data, size_t length); + +size_t tox_options_get_savedata_length(const struct Tox_Options *options); + +void tox_options_set_savedata_length(struct Tox_Options *options, size_t length); + +tox_log_cb *tox_options_get_log_callback(const struct Tox_Options *options); + +void tox_options_set_log_callback(struct Tox_Options *options, tox_log_cb *callback); + +void *tox_options_get_log_user_data(const struct Tox_Options *options); + +void tox_options_set_log_user_data(struct Tox_Options *options, void *user_data); + +/** + * Initialises a Tox_Options object with the default options. + * + * The result of this function is independent of the original options. All + * values will be overwritten, no values will be read (so it is permissible + * to pass an uninitialised object). + * + * If options is NULL, this function has no effect. + * + * @param options An options object to be filled with default options. + */ +void tox_options_default(struct Tox_Options *options); + +typedef enum TOX_ERR_OPTIONS_NEW { + + /** + * The function returned successfully. + */ + TOX_ERR_OPTIONS_NEW_OK, + + /** + * The function failed to allocate enough memory for the options struct. + */ + TOX_ERR_OPTIONS_NEW_MALLOC, + +} TOX_ERR_OPTIONS_NEW; + + +/** + * Allocates a new Tox_Options object and initialises it with the default + * options. This function can be used to preserve long term ABI compatibility by + * giving the responsibility of allocation and deallocation to the Tox library. + * + * Objects returned from this function must be freed using the tox_options_free + * function. + * + * @return A new Tox_Options object with default options or NULL on failure. + */ +struct Tox_Options *tox_options_new(TOX_ERR_OPTIONS_NEW *error); + +/** + * Releases all resources associated with an options objects. + * + * Passing a pointer that was not returned by tox_options_new results in + * undefined behaviour. + */ +void tox_options_free(struct Tox_Options *options); + + +/******************************************************************************* + * + * :: Creation and destruction + * + ******************************************************************************/ + + + +typedef enum TOX_ERR_NEW { + + /** + * The function returned successfully. + */ + TOX_ERR_NEW_OK, + + /** + * One of the arguments to the function was NULL when it was not expected. + */ + TOX_ERR_NEW_NULL, + + /** + * The function was unable to allocate enough memory to store the internal + * structures for the Tox object. + */ + TOX_ERR_NEW_MALLOC, + + /** + * The function was unable to bind to a port. This may mean that all ports + * have already been bound, e.g. by other Tox instances, or it may mean + * a permission error. You may be able to gather more information from errno. + */ + TOX_ERR_NEW_PORT_ALLOC, + + /** + * proxy_type was invalid. + */ + TOX_ERR_NEW_PROXY_BAD_TYPE, + + /** + * proxy_type was valid but the proxy_host passed had an invalid format + * or was NULL. + */ + TOX_ERR_NEW_PROXY_BAD_HOST, + + /** + * proxy_type was valid, but the proxy_port was invalid. + */ + TOX_ERR_NEW_PROXY_BAD_PORT, + + /** + * The proxy address passed could not be resolved. + */ + TOX_ERR_NEW_PROXY_NOT_FOUND, + + /** + * The byte array to be loaded contained an encrypted save. + */ + TOX_ERR_NEW_LOAD_ENCRYPTED, + + /** + * The data format was invalid. This can happen when loading data that was + * saved by an older version of Tox, or when the data has been corrupted. + * When loading from badly formatted data, some data may have been loaded, + * and the rest is discarded. Passing an invalid length parameter also + * causes this error. + */ + TOX_ERR_NEW_LOAD_BAD_FORMAT, + +} TOX_ERR_NEW; + + +/** + * @brief Creates and initialises a new Tox instance with the options passed. + * + * This function will bring the instance into a valid state. Running the event + * loop with a new instance will operate correctly. + * + * If loading failed or succeeded only partially, the new or partially loaded + * instance is returned and an error code is set. + * + * @param options An options object as described above. If this parameter is + * NULL, the default options are used. + * + * @see tox_iterate for the event loop. + * + * @return A new Tox instance pointer on success or NULL on failure. + */ +Tox *tox_new(const struct Tox_Options *options, TOX_ERR_NEW *error); + +/** + * Releases all resources associated with the Tox instance and disconnects from + * the network. + * + * After calling this function, the Tox pointer becomes invalid. No other + * functions can be called, and the pointer value can no longer be read. + */ +void tox_kill(Tox *tox); + +/** + * Calculates the number of bytes required to store the tox instance with + * tox_get_savedata. This function cannot fail. The result is always greater than 0. + * + * @see threading for concurrency implications. + */ +size_t tox_get_savedata_size(const Tox *tox); + +/** + * Store all information associated with the tox instance to a byte array. + * + * @param savedata A memory region large enough to store the tox instance + * data. Call tox_get_savedata_size to find the number of bytes required. If this parameter + * is NULL, this function has no effect. + */ +void tox_get_savedata(const Tox *tox, uint8_t *savedata); + + +/******************************************************************************* + * + * :: Connection lifecycle and event loop + * + ******************************************************************************/ + + + +typedef enum TOX_ERR_BOOTSTRAP { + + /** + * The function returned successfully. + */ + TOX_ERR_BOOTSTRAP_OK, + + /** + * One of the arguments to the function was NULL when it was not expected. + */ + TOX_ERR_BOOTSTRAP_NULL, + + /** + * The address could not be resolved to an IP address, or the IP address + * passed was invalid. + */ + TOX_ERR_BOOTSTRAP_BAD_HOST, + + /** + * The port passed was invalid. The valid port range is (1, 65535). + */ + TOX_ERR_BOOTSTRAP_BAD_PORT, + +} TOX_ERR_BOOTSTRAP; + + +/** + * Sends a "get nodes" request to the given bootstrap node with IP, port, and + * public key to setup connections. + * + * This function will attempt to connect to the node using UDP. You must use + * this function even if Tox_Options.udp_enabled was set to false. + * + * @param address The hostname or IP address (IPv4 or IPv6) of the node. + * @param port The port on the host on which the bootstrap Tox instance is + * listening. + * @param public_key The long term public key of the bootstrap node + * (TOX_PUBLIC_KEY_SIZE bytes). + * @return true on success. + */ +bool tox_bootstrap(Tox *tox, const char *address, uint16_t port, const uint8_t *public_key, TOX_ERR_BOOTSTRAP *error); + +/** + * Adds additional host:port pair as TCP relay. + * + * This function can be used to initiate TCP connections to different ports on + * the same bootstrap node, or to add TCP relays without using them as + * bootstrap nodes. + * + * @param address The hostname or IP address (IPv4 or IPv6) of the TCP relay. + * @param port The port on the host on which the TCP relay is listening. + * @param public_key The long term public key of the TCP relay + * (TOX_PUBLIC_KEY_SIZE bytes). + * @return true on success. + */ +bool tox_add_tcp_relay(Tox *tox, const char *address, uint16_t port, const uint8_t *public_key, + TOX_ERR_BOOTSTRAP *error); + +/** + * Protocols that can be used to connect to the network or friends. + */ +typedef enum TOX_CONNECTION { + + /** + * There is no connection. This instance, or the friend the state change is + * about, is now offline. + */ + TOX_CONNECTION_NONE, + + /** + * A TCP connection has been established. For the own instance, this means it + * is connected through a TCP relay, only. For a friend, this means that the + * connection to that particular friend goes through a TCP relay. + */ + TOX_CONNECTION_TCP, + + /** + * A UDP connection has been established. For the own instance, this means it + * is able to send UDP packets to DHT nodes, but may still be connected to + * a TCP relay. For a friend, this means that the connection to that + * particular friend was built using direct UDP packets. + */ + TOX_CONNECTION_UDP, + +} TOX_CONNECTION; + + +/** + * Return whether we are connected to the DHT. The return value is equal to the + * last value received through the `self_connection_status` callback. + */ +TOX_CONNECTION tox_self_get_connection_status(const Tox *tox); + +/** + * @param connection_status Whether we are connected to the DHT. + */ +typedef void tox_self_connection_status_cb(Tox *tox, TOX_CONNECTION connection_status, void *user_data); + + +/** + * Set the callback for the `self_connection_status` event. Pass NULL to unset. + * + * This event is triggered whenever there is a change in the DHT connection + * state. When disconnected, a client may choose to call tox_bootstrap again, to + * reconnect to the DHT. Note that this state may frequently change for short + * amounts of time. Clients should therefore not immediately bootstrap on + * receiving a disconnect. + * + * TODO(iphydf): how long should a client wait before bootstrapping again? + */ +void tox_callback_self_connection_status(Tox *tox, tox_self_connection_status_cb *callback); + +/** + * Return the time in milliseconds before tox_iterate() should be called again + * for optimal performance. + */ +uint32_t tox_iteration_interval(const Tox *tox); + +/** + * The main loop that needs to be run in intervals of tox_iteration_interval() + * milliseconds. + */ +void tox_iterate(Tox *tox, void *user_data); + + +/******************************************************************************* + * + * :: Internal client information (Tox address/id) + * + ******************************************************************************/ + + + +/** + * Writes the Tox friend address of the client to a byte array. The address is + * not in human-readable format. If a client wants to display the address, + * formatting is required. + * + * @param address A memory region of at least TOX_ADDRESS_SIZE bytes. If this + * parameter is NULL, this function has no effect. + * @see TOX_ADDRESS_SIZE for the address format. + */ +void tox_self_get_address(const Tox *tox, uint8_t *address); + +/** + * Set the 4-byte nospam part of the address. This value is expected in host + * byte order. I.e. 0x12345678 will form the bytes [12, 34, 56, 78] in the + * nospam part of the Tox friend address. + * + * @param nospam Any 32 bit unsigned integer. + */ +void tox_self_set_nospam(Tox *tox, uint32_t nospam); + +/** + * Get the 4-byte nospam part of the address. This value is returned in host + * byte order. + */ +uint32_t tox_self_get_nospam(const Tox *tox); + +/** + * Copy the Tox Public Key (long term) from the Tox object. + * + * @param public_key A memory region of at least TOX_PUBLIC_KEY_SIZE bytes. If + * this parameter is NULL, this function has no effect. + */ +void tox_self_get_public_key(const Tox *tox, uint8_t *public_key); + +/** + * Copy the Tox Secret Key from the Tox object. + * + * @param secret_key A memory region of at least TOX_SECRET_KEY_SIZE bytes. If + * this parameter is NULL, this function has no effect. + */ +void tox_self_get_secret_key(const Tox *tox, uint8_t *secret_key); + + +/******************************************************************************* + * + * :: User-visible client information (nickname/status) + * + ******************************************************************************/ + + + +/** + * Common error codes for all functions that set a piece of user-visible + * client information. + */ +typedef enum TOX_ERR_SET_INFO { + + /** + * The function returned successfully. + */ + TOX_ERR_SET_INFO_OK, + + /** + * One of the arguments to the function was NULL when it was not expected. + */ + TOX_ERR_SET_INFO_NULL, + + /** + * Information length exceeded maximum permissible size. + */ + TOX_ERR_SET_INFO_TOO_LONG, + +} TOX_ERR_SET_INFO; + + +/** + * Set the nickname for the Tox client. + * + * Nickname length cannot exceed TOX_MAX_NAME_LENGTH. If length is 0, the name + * parameter is ignored (it can be NULL), and the nickname is set back to empty. + * + * @param name A byte array containing the new nickname. + * @param length The size of the name byte array. + * + * @return true on success. + */ +bool tox_self_set_name(Tox *tox, const uint8_t *name, size_t length, TOX_ERR_SET_INFO *error); + +/** + * Return the length of the current nickname as passed to tox_self_set_name. + * + * If no nickname was set before calling this function, the name is empty, + * and this function returns 0. + * + * @see threading for concurrency implications. + */ +size_t tox_self_get_name_size(const Tox *tox); + +/** + * Write the nickname set by tox_self_set_name to a byte array. + * + * If no nickname was set before calling this function, the name is empty, + * and this function has no effect. + * + * Call tox_self_get_name_size to find out how much memory to allocate for + * the result. + * + * @param name A valid memory location large enough to hold the nickname. + * If this parameter is NULL, the function has no effect. + */ +void tox_self_get_name(const Tox *tox, uint8_t *name); + +/** + * Set the client's status message. + * + * Status message length cannot exceed TOX_MAX_STATUS_MESSAGE_LENGTH. If + * length is 0, the status parameter is ignored (it can be NULL), and the + * user status is set back to empty. + */ +bool tox_self_set_status_message(Tox *tox, const uint8_t *status_message, size_t length, TOX_ERR_SET_INFO *error); + +/** + * Return the length of the current status message as passed to tox_self_set_status_message. + * + * If no status message was set before calling this function, the status + * is empty, and this function returns 0. + * + * @see threading for concurrency implications. + */ +size_t tox_self_get_status_message_size(const Tox *tox); + +/** + * Write the status message set by tox_self_set_status_message to a byte array. + * + * If no status message was set before calling this function, the status is + * empty, and this function has no effect. + * + * Call tox_self_get_status_message_size to find out how much memory to allocate for + * the result. + * + * @param status_message A valid memory location large enough to hold the + * status message. If this parameter is NULL, the function has no effect. + */ +void tox_self_get_status_message(const Tox *tox, uint8_t *status_message); + +/** + * Set the client's user status. + * + * @param status One of the user statuses listed in the enumeration above. + */ +void tox_self_set_status(Tox *tox, TOX_USER_STATUS status); + +/** + * Returns the client's user status. + */ +TOX_USER_STATUS tox_self_get_status(const Tox *tox); + + +/******************************************************************************* + * + * :: Friend list management + * + ******************************************************************************/ + + + +typedef enum TOX_ERR_FRIEND_ADD { + + /** + * The function returned successfully. + */ + TOX_ERR_FRIEND_ADD_OK, + + /** + * One of the arguments to the function was NULL when it was not expected. + */ + TOX_ERR_FRIEND_ADD_NULL, + + /** + * The length of the friend request message exceeded + * TOX_MAX_FRIEND_REQUEST_LENGTH. + */ + TOX_ERR_FRIEND_ADD_TOO_LONG, + + /** + * The friend request message was empty. This, and the TOO_LONG code will + * never be returned from tox_friend_add_norequest. + */ + TOX_ERR_FRIEND_ADD_NO_MESSAGE, + + /** + * The friend address belongs to the sending client. + */ + TOX_ERR_FRIEND_ADD_OWN_KEY, + + /** + * A friend request has already been sent, or the address belongs to a friend + * that is already on the friend list. + */ + TOX_ERR_FRIEND_ADD_ALREADY_SENT, + + /** + * The friend address checksum failed. + */ + TOX_ERR_FRIEND_ADD_BAD_CHECKSUM, + + /** + * The friend was already there, but the nospam value was different. + */ + TOX_ERR_FRIEND_ADD_SET_NEW_NOSPAM, + + /** + * A memory allocation failed when trying to increase the friend list size. + */ + TOX_ERR_FRIEND_ADD_MALLOC, + +} TOX_ERR_FRIEND_ADD; + + +/** + * Add a friend to the friend list and send a friend request. + * + * A friend request message must be at least 1 byte long and at most + * TOX_MAX_FRIEND_REQUEST_LENGTH. + * + * Friend numbers are unique identifiers used in all functions that operate on + * friends. Once added, a friend number is stable for the lifetime of the Tox + * object. After saving the state and reloading it, the friend numbers may not + * be the same as before. Deleting a friend creates a gap in the friend number + * set, which is filled by the next adding of a friend. Any pattern in friend + * numbers should not be relied on. + * + * If more than INT32_MAX friends are added, this function causes undefined + * behaviour. + * + * @param address The address of the friend (returned by tox_self_get_address of + * the friend you wish to add) it must be TOX_ADDRESS_SIZE bytes. + * @param message The message that will be sent along with the friend request. + * @param length The length of the data byte array. + * + * @return the friend number on success, UINT32_MAX on failure. + */ +uint32_t tox_friend_add(Tox *tox, const uint8_t *address, const uint8_t *message, size_t length, + TOX_ERR_FRIEND_ADD *error); + +/** + * Add a friend without sending a friend request. + * + * This function is used to add a friend in response to a friend request. If the + * client receives a friend request, it can be reasonably sure that the other + * client added this client as a friend, eliminating the need for a friend + * request. + * + * This function is also useful in a situation where both instances are + * controlled by the same entity, so that this entity can perform the mutual + * friend adding. In this case, there is no need for a friend request, either. + * + * @param public_key A byte array of length TOX_PUBLIC_KEY_SIZE containing the + * Public Key (not the Address) of the friend to add. + * + * @return the friend number on success, UINT32_MAX on failure. + * @see tox_friend_add for a more detailed description of friend numbers. + */ +uint32_t tox_friend_add_norequest(Tox *tox, const uint8_t *public_key, TOX_ERR_FRIEND_ADD *error); + +typedef enum TOX_ERR_FRIEND_DELETE { + + /** + * The function returned successfully. + */ + TOX_ERR_FRIEND_DELETE_OK, + + /** + * There was no friend with the given friend number. No friends were deleted. + */ + TOX_ERR_FRIEND_DELETE_FRIEND_NOT_FOUND, + +} TOX_ERR_FRIEND_DELETE; + + +/** + * Remove a friend from the friend list. + * + * This does not notify the friend of their deletion. After calling this + * function, this client will appear offline to the friend and no communication + * can occur between the two. + * + * @param friend_number Friend number for the friend to be deleted. + * + * @return true on success. + */ +bool tox_friend_delete(Tox *tox, uint32_t friend_number, TOX_ERR_FRIEND_DELETE *error); + + +/******************************************************************************* + * + * :: Friend list queries + * + ******************************************************************************/ + + + +typedef enum TOX_ERR_FRIEND_BY_PUBLIC_KEY { + + /** + * The function returned successfully. + */ + TOX_ERR_FRIEND_BY_PUBLIC_KEY_OK, + + /** + * One of the arguments to the function was NULL when it was not expected. + */ + TOX_ERR_FRIEND_BY_PUBLIC_KEY_NULL, + + /** + * No friend with the given Public Key exists on the friend list. + */ + TOX_ERR_FRIEND_BY_PUBLIC_KEY_NOT_FOUND, + +} TOX_ERR_FRIEND_BY_PUBLIC_KEY; + + +/** + * Return the friend number associated with that Public Key. + * + * @return the friend number on success, UINT32_MAX on failure. + * @param public_key A byte array containing the Public Key. + */ +uint32_t tox_friend_by_public_key(const Tox *tox, const uint8_t *public_key, TOX_ERR_FRIEND_BY_PUBLIC_KEY *error); + +/** + * Checks if a friend with the given friend number exists and returns true if + * it does. + */ +bool tox_friend_exists(const Tox *tox, uint32_t friend_number); + +/** + * Return the number of friends on the friend list. + * + * This function can be used to determine how much memory to allocate for + * tox_self_get_friend_list. + */ +size_t tox_self_get_friend_list_size(const Tox *tox); + +/** + * Copy a list of valid friend numbers into an array. + * + * Call tox_self_get_friend_list_size to determine the number of elements to allocate. + * + * @param friend_list A memory region with enough space to hold the friend + * list. If this parameter is NULL, this function has no effect. + */ +void tox_self_get_friend_list(const Tox *tox, uint32_t *friend_list); + +typedef enum TOX_ERR_FRIEND_GET_PUBLIC_KEY { + + /** + * The function returned successfully. + */ + TOX_ERR_FRIEND_GET_PUBLIC_KEY_OK, + + /** + * No friend with the given number exists on the friend list. + */ + TOX_ERR_FRIEND_GET_PUBLIC_KEY_FRIEND_NOT_FOUND, + +} TOX_ERR_FRIEND_GET_PUBLIC_KEY; + + +/** + * Copies the Public Key associated with a given friend number to a byte array. + * + * @param friend_number The friend number you want the Public Key of. + * @param public_key A memory region of at least TOX_PUBLIC_KEY_SIZE bytes. If + * this parameter is NULL, this function has no effect. + * + * @return true on success. + */ +bool tox_friend_get_public_key(const Tox *tox, uint32_t friend_number, uint8_t *public_key, + TOX_ERR_FRIEND_GET_PUBLIC_KEY *error); + +typedef enum TOX_ERR_FRIEND_GET_LAST_ONLINE { + + /** + * The function returned successfully. + */ + TOX_ERR_FRIEND_GET_LAST_ONLINE_OK, + + /** + * No friend with the given number exists on the friend list. + */ + TOX_ERR_FRIEND_GET_LAST_ONLINE_FRIEND_NOT_FOUND, + +} TOX_ERR_FRIEND_GET_LAST_ONLINE; + + +/** + * Return a unix-time timestamp of the last time the friend associated with a given + * friend number was seen online. This function will return UINT64_MAX on error. + * + * @param friend_number The friend number you want to query. + */ +uint64_t tox_friend_get_last_online(const Tox *tox, uint32_t friend_number, TOX_ERR_FRIEND_GET_LAST_ONLINE *error); + + +/******************************************************************************* + * + * :: Friend-specific state queries (can also be received through callbacks) + * + ******************************************************************************/ + + + +/** + * Common error codes for friend state query functions. + */ +typedef enum TOX_ERR_FRIEND_QUERY { + + /** + * The function returned successfully. + */ + TOX_ERR_FRIEND_QUERY_OK, + + /** + * The pointer parameter for storing the query result (name, message) was + * NULL. Unlike the `_self_` variants of these functions, which have no effect + * when a parameter is NULL, these functions return an error in that case. + */ + TOX_ERR_FRIEND_QUERY_NULL, + + /** + * The friend_number did not designate a valid friend. + */ + TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND, + +} TOX_ERR_FRIEND_QUERY; + + +/** + * Return the length of the friend's name. If the friend number is invalid, the + * return value is unspecified. + * + * The return value is equal to the `length` argument received by the last + * `friend_name` callback. + */ +size_t tox_friend_get_name_size(const Tox *tox, uint32_t friend_number, TOX_ERR_FRIEND_QUERY *error); + +/** + * Write the name of the friend designated by the given friend number to a byte + * array. + * + * Call tox_friend_get_name_size to determine the allocation size for the `name` + * parameter. + * + * The data written to `name` is equal to the data received by the last + * `friend_name` callback. + * + * @param name A valid memory region large enough to store the friend's name. + * + * @return true on success. + */ +bool tox_friend_get_name(const Tox *tox, uint32_t friend_number, uint8_t *name, TOX_ERR_FRIEND_QUERY *error); + +/** + * @param friend_number The friend number of the friend whose name changed. + * @param name A byte array containing the same data as + * tox_friend_get_name would write to its `name` parameter. + * @param length A value equal to the return value of + * tox_friend_get_name_size. + */ +typedef void tox_friend_name_cb(Tox *tox, uint32_t friend_number, const uint8_t *name, size_t length, void *user_data); + + +/** + * Set the callback for the `friend_name` event. Pass NULL to unset. + * + * This event is triggered when a friend changes their name. + */ +void tox_callback_friend_name(Tox *tox, tox_friend_name_cb *callback); + +/** + * Return the length of the friend's status message. If the friend number is + * invalid, the return value is SIZE_MAX. + */ +size_t tox_friend_get_status_message_size(const Tox *tox, uint32_t friend_number, TOX_ERR_FRIEND_QUERY *error); + +/** + * Write the status message of the friend designated by the given friend number to a byte + * array. + * + * Call tox_friend_get_status_message_size to determine the allocation size for the `status_name` + * parameter. + * + * The data written to `status_message` is equal to the data received by the last + * `friend_status_message` callback. + * + * @param status_message A valid memory region large enough to store the friend's status message. + */ +bool tox_friend_get_status_message(const Tox *tox, uint32_t friend_number, uint8_t *status_message, + TOX_ERR_FRIEND_QUERY *error); + +/** + * @param friend_number The friend number of the friend whose status message + * changed. + * @param message A byte array containing the same data as + * tox_friend_get_status_message would write to its `status_message` parameter. + * @param length A value equal to the return value of + * tox_friend_get_status_message_size. + */ +typedef void tox_friend_status_message_cb(Tox *tox, uint32_t friend_number, const uint8_t *message, size_t length, + void *user_data); + + +/** + * Set the callback for the `friend_status_message` event. Pass NULL to unset. + * + * This event is triggered when a friend changes their status message. + */ +void tox_callback_friend_status_message(Tox *tox, tox_friend_status_message_cb *callback); + +/** + * Return the friend's user status (away/busy/...). If the friend number is + * invalid, the return value is unspecified. + * + * The status returned is equal to the last status received through the + * `friend_status` callback. + */ +TOX_USER_STATUS tox_friend_get_status(const Tox *tox, uint32_t friend_number, TOX_ERR_FRIEND_QUERY *error); + +/** + * @param friend_number The friend number of the friend whose user status + * changed. + * @param status The new user status. + */ +typedef void tox_friend_status_cb(Tox *tox, uint32_t friend_number, TOX_USER_STATUS status, void *user_data); + + +/** + * Set the callback for the `friend_status` event. Pass NULL to unset. + * + * This event is triggered when a friend changes their user status. + */ +void tox_callback_friend_status(Tox *tox, tox_friend_status_cb *callback); + +/** + * Check whether a friend is currently connected to this client. + * + * The result of this function is equal to the last value received by the + * `friend_connection_status` callback. + * + * @param friend_number The friend number for which to query the connection + * status. + * + * @return the friend's connection status as it was received through the + * `friend_connection_status` event. + */ +TOX_CONNECTION tox_friend_get_connection_status(const Tox *tox, uint32_t friend_number, TOX_ERR_FRIEND_QUERY *error); + +/** + * @param friend_number The friend number of the friend whose connection status + * changed. + * @param connection_status The result of calling + * tox_friend_get_connection_status on the passed friend_number. + */ +typedef void tox_friend_connection_status_cb(Tox *tox, uint32_t friend_number, TOX_CONNECTION connection_status, + void *user_data); + + +/** + * Set the callback for the `friend_connection_status` event. Pass NULL to unset. + * + * This event is triggered when a friend goes offline after having been online, + * or when a friend goes online. + * + * This callback is not called when adding friends. It is assumed that when + * adding friends, their connection status is initially offline. + */ +void tox_callback_friend_connection_status(Tox *tox, tox_friend_connection_status_cb *callback); + +/** + * Check whether a friend is currently typing a message. + * + * @param friend_number The friend number for which to query the typing status. + * + * @return true if the friend is typing. + * @return false if the friend is not typing, or the friend number was + * invalid. Inspect the error code to determine which case it is. + */ +bool tox_friend_get_typing(const Tox *tox, uint32_t friend_number, TOX_ERR_FRIEND_QUERY *error); + +/** + * @param friend_number The friend number of the friend who started or stopped + * typing. + * @param is_typing The result of calling tox_friend_get_typing on the passed + * friend_number. + */ +typedef void tox_friend_typing_cb(Tox *tox, uint32_t friend_number, bool is_typing, void *user_data); + + +/** + * Set the callback for the `friend_typing` event. Pass NULL to unset. + * + * This event is triggered when a friend starts or stops typing. + */ +void tox_callback_friend_typing(Tox *tox, tox_friend_typing_cb *callback); + + +/******************************************************************************* + * + * :: Sending private messages + * + ******************************************************************************/ + + + +typedef enum TOX_ERR_SET_TYPING { + + /** + * The function returned successfully. + */ + TOX_ERR_SET_TYPING_OK, + + /** + * The friend number did not designate a valid friend. + */ + TOX_ERR_SET_TYPING_FRIEND_NOT_FOUND, + +} TOX_ERR_SET_TYPING; + + +/** + * Set the client's typing status for a friend. + * + * The client is responsible for turning it on or off. + * + * @param friend_number The friend to which the client is typing a message. + * @param typing The typing status. True means the client is typing. + * + * @return true on success. + */ +bool tox_self_set_typing(Tox *tox, uint32_t friend_number, bool typing, TOX_ERR_SET_TYPING *error); + +typedef enum TOX_ERR_FRIEND_SEND_MESSAGE { + + /** + * The function returned successfully. + */ + TOX_ERR_FRIEND_SEND_MESSAGE_OK, + + /** + * One of the arguments to the function was NULL when it was not expected. + */ + TOX_ERR_FRIEND_SEND_MESSAGE_NULL, + + /** + * The friend number did not designate a valid friend. + */ + TOX_ERR_FRIEND_SEND_MESSAGE_FRIEND_NOT_FOUND, + + /** + * This client is currently not connected to the friend. + */ + TOX_ERR_FRIEND_SEND_MESSAGE_FRIEND_NOT_CONNECTED, + + /** + * An allocation error occurred while increasing the send queue size. + */ + TOX_ERR_FRIEND_SEND_MESSAGE_SENDQ, + + /** + * Message length exceeded TOX_MAX_MESSAGE_LENGTH. + */ + TOX_ERR_FRIEND_SEND_MESSAGE_TOO_LONG, + + /** + * Attempted to send a zero-length message. + */ + TOX_ERR_FRIEND_SEND_MESSAGE_EMPTY, + +} TOX_ERR_FRIEND_SEND_MESSAGE; + + +/** + * Send a text chat message to an online friend. + * + * This function creates a chat message packet and pushes it into the send + * queue. + * + * The message length may not exceed TOX_MAX_MESSAGE_LENGTH. Larger messages + * must be split by the client and sent as separate messages. Other clients can + * then reassemble the fragments. Messages may not be empty. + * + * The return value of this function is the message ID. If a read receipt is + * received, the triggered `friend_read_receipt` event will be passed this message ID. + * + * Message IDs are unique per friend. The first message ID is 0. Message IDs are + * incremented by 1 each time a message is sent. If UINT32_MAX messages were + * sent, the next message ID is 0. + * + * @param type Message type (normal, action, ...). + * @param friend_number The friend number of the friend to send the message to. + * @param message A non-NULL pointer to the first element of a byte array + * containing the message text. + * @param length Length of the message to be sent. + */ +uint32_t tox_friend_send_message(Tox *tox, uint32_t friend_number, TOX_MESSAGE_TYPE type, const uint8_t *message, + size_t length, TOX_ERR_FRIEND_SEND_MESSAGE *error); + +/** + * @param friend_number The friend number of the friend who received the message. + * @param message_id The message ID as returned from tox_friend_send_message + * corresponding to the message sent. + */ +typedef void tox_friend_read_receipt_cb(Tox *tox, uint32_t friend_number, uint32_t message_id, void *user_data); + + +/** + * Set the callback for the `friend_read_receipt` event. Pass NULL to unset. + * + * This event is triggered when the friend receives the message sent with + * tox_friend_send_message with the corresponding message ID. + */ +void tox_callback_friend_read_receipt(Tox *tox, tox_friend_read_receipt_cb *callback); + + +/******************************************************************************* + * + * :: Receiving private messages and friend requests + * + ******************************************************************************/ + + + +/** + * @param public_key The Public Key of the user who sent the friend request. + * @param message The message they sent along with the request. + * @param length The size of the message byte array. + */ +typedef void tox_friend_request_cb(Tox *tox, const uint8_t *public_key, const uint8_t *message, size_t length, + void *user_data); + + +/** + * Set the callback for the `friend_request` event. Pass NULL to unset. + * + * This event is triggered when a friend request is received. + */ +void tox_callback_friend_request(Tox *tox, tox_friend_request_cb *callback); + +/** + * @param friend_number The friend number of the friend who sent the message. + * @param message The message data they sent. + * @param length The size of the message byte array. + */ +typedef void tox_friend_message_cb(Tox *tox, uint32_t friend_number, TOX_MESSAGE_TYPE type, const uint8_t *message, + size_t length, void *user_data); + + +/** + * Set the callback for the `friend_message` event. Pass NULL to unset. + * + * This event is triggered when a message from a friend is received. + */ +void tox_callback_friend_message(Tox *tox, tox_friend_message_cb *callback); + + +/******************************************************************************* + * + * :: File transmission: common between sending and receiving + * + ******************************************************************************/ + + + +/** + * Generates a cryptographic hash of the given data. + * + * This function may be used by clients for any purpose, but is provided + * primarily for validating cached avatars. This use is highly recommended to + * avoid unnecessary avatar updates. + * + * If hash is NULL or data is NULL while length is not 0 the function returns false, + * otherwise it returns true. + * + * This function is a wrapper to internal message-digest functions. + * + * @param hash A valid memory location the hash data. It must be at least + * TOX_HASH_LENGTH bytes in size. + * @param data Data to be hashed or NULL. + * @param length Size of the data array or 0. + * + * @return true if hash was not NULL. + */ +bool tox_hash(uint8_t *hash, const uint8_t *data, size_t length); + +enum TOX_FILE_KIND { + + /** + * Arbitrary file data. Clients can choose to handle it based on the file name + * or magic or any other way they choose. + */ + TOX_FILE_KIND_DATA, + + /** + * Avatar file_id. This consists of tox_hash(image). + * Avatar data. This consists of the image data. + * + * Avatars can be sent at any time the client wishes. Generally, a client will + * send the avatar to a friend when that friend comes online, and to all + * friends when the avatar changed. A client can save some traffic by + * remembering which friend received the updated avatar already and only send + * it if the friend has an out of date avatar. + * + * Clients who receive avatar send requests can reject it (by sending + * TOX_FILE_CONTROL_CANCEL before any other controls), or accept it (by + * sending TOX_FILE_CONTROL_RESUME). The file_id of length TOX_HASH_LENGTH bytes + * (same length as TOX_FILE_ID_LENGTH) will contain the hash. A client can compare + * this hash with a saved hash and send TOX_FILE_CONTROL_CANCEL to terminate the avatar + * transfer if it matches. + * + * When file_size is set to 0 in the transfer request it means that the client + * has no avatar. + */ + TOX_FILE_KIND_AVATAR, + +}; + + +typedef enum TOX_FILE_CONTROL { + + /** + * Sent by the receiving side to accept a file send request. Also sent after a + * TOX_FILE_CONTROL_PAUSE command to continue sending or receiving. + */ + TOX_FILE_CONTROL_RESUME, + + /** + * Sent by clients to pause the file transfer. The initial state of a file + * transfer is always paused on the receiving side and running on the sending + * side. If both the sending and receiving side pause the transfer, then both + * need to send TOX_FILE_CONTROL_RESUME for the transfer to resume. + */ + TOX_FILE_CONTROL_PAUSE, + + /** + * Sent by the receiving side to reject a file send request before any other + * commands are sent. Also sent by either side to terminate a file transfer. + */ + TOX_FILE_CONTROL_CANCEL, + +} TOX_FILE_CONTROL; + + +typedef enum TOX_ERR_FILE_CONTROL { + + /** + * The function returned successfully. + */ + TOX_ERR_FILE_CONTROL_OK, + + /** + * The friend_number passed did not designate a valid friend. + */ + TOX_ERR_FILE_CONTROL_FRIEND_NOT_FOUND, + + /** + * This client is currently not connected to the friend. + */ + TOX_ERR_FILE_CONTROL_FRIEND_NOT_CONNECTED, + + /** + * No file transfer with the given file number was found for the given friend. + */ + TOX_ERR_FILE_CONTROL_NOT_FOUND, + + /** + * A RESUME control was sent, but the file transfer is running normally. + */ + TOX_ERR_FILE_CONTROL_NOT_PAUSED, + + /** + * A RESUME control was sent, but the file transfer was paused by the other + * party. Only the party that paused the transfer can resume it. + */ + TOX_ERR_FILE_CONTROL_DENIED, + + /** + * A PAUSE control was sent, but the file transfer was already paused. + */ + TOX_ERR_FILE_CONTROL_ALREADY_PAUSED, + + /** + * Packet queue is full. + */ + TOX_ERR_FILE_CONTROL_SENDQ, + +} TOX_ERR_FILE_CONTROL; + + +/** + * Sends a file control command to a friend for a given file transfer. + * + * @param friend_number The friend number of the friend the file is being + * transferred to or received from. + * @param file_number The friend-specific identifier for the file transfer. + * @param control The control command to send. + * + * @return true on success. + */ +bool tox_file_control(Tox *tox, uint32_t friend_number, uint32_t file_number, TOX_FILE_CONTROL control, + TOX_ERR_FILE_CONTROL *error); + +/** + * When receiving TOX_FILE_CONTROL_CANCEL, the client should release the + * resources associated with the file number and consider the transfer failed. + * + * @param friend_number The friend number of the friend who is sending the file. + * @param file_number The friend-specific file number the data received is + * associated with. + * @param control The file control command received. + */ +typedef void tox_file_recv_control_cb(Tox *tox, uint32_t friend_number, uint32_t file_number, TOX_FILE_CONTROL control, + void *user_data); + + +/** + * Set the callback for the `file_recv_control` event. Pass NULL to unset. + * + * This event is triggered when a file control command is received from a + * friend. + */ +void tox_callback_file_recv_control(Tox *tox, tox_file_recv_control_cb *callback); + +typedef enum TOX_ERR_FILE_SEEK { + + /** + * The function returned successfully. + */ + TOX_ERR_FILE_SEEK_OK, + + /** + * The friend_number passed did not designate a valid friend. + */ + TOX_ERR_FILE_SEEK_FRIEND_NOT_FOUND, + + /** + * This client is currently not connected to the friend. + */ + TOX_ERR_FILE_SEEK_FRIEND_NOT_CONNECTED, + + /** + * No file transfer with the given file number was found for the given friend. + */ + TOX_ERR_FILE_SEEK_NOT_FOUND, + + /** + * File was not in a state where it could be seeked. + */ + TOX_ERR_FILE_SEEK_DENIED, + + /** + * Seek position was invalid + */ + TOX_ERR_FILE_SEEK_INVALID_POSITION, + + /** + * Packet queue is full. + */ + TOX_ERR_FILE_SEEK_SENDQ, + +} TOX_ERR_FILE_SEEK; + + +/** + * Sends a file seek control command to a friend for a given file transfer. + * + * This function can only be called to resume a file transfer right before + * TOX_FILE_CONTROL_RESUME is sent. + * + * @param friend_number The friend number of the friend the file is being + * received from. + * @param file_number The friend-specific identifier for the file transfer. + * @param position The position that the file should be seeked to. + */ +bool tox_file_seek(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, TOX_ERR_FILE_SEEK *error); + +typedef enum TOX_ERR_FILE_GET { + + /** + * The function returned successfully. + */ + TOX_ERR_FILE_GET_OK, + + /** + * One of the arguments to the function was NULL when it was not expected. + */ + TOX_ERR_FILE_GET_NULL, + + /** + * The friend_number passed did not designate a valid friend. + */ + TOX_ERR_FILE_GET_FRIEND_NOT_FOUND, + + /** + * No file transfer with the given file number was found for the given friend. + */ + TOX_ERR_FILE_GET_NOT_FOUND, + +} TOX_ERR_FILE_GET; + + +/** + * Copy the file id associated to the file transfer to a byte array. + * + * @param friend_number The friend number of the friend the file is being + * transferred to or received from. + * @param file_number The friend-specific identifier for the file transfer. + * @param file_id A memory region of at least TOX_FILE_ID_LENGTH bytes. If + * this parameter is NULL, this function has no effect. + * + * @return true on success. + */ +bool tox_file_get_file_id(const Tox *tox, uint32_t friend_number, uint32_t file_number, uint8_t *file_id, + TOX_ERR_FILE_GET *error); + + +/******************************************************************************* + * + * :: File transmission: sending + * + ******************************************************************************/ + + + +typedef enum TOX_ERR_FILE_SEND { + + /** + * The function returned successfully. + */ + TOX_ERR_FILE_SEND_OK, + + /** + * One of the arguments to the function was NULL when it was not expected. + */ + TOX_ERR_FILE_SEND_NULL, + + /** + * The friend_number passed did not designate a valid friend. + */ + TOX_ERR_FILE_SEND_FRIEND_NOT_FOUND, + + /** + * This client is currently not connected to the friend. + */ + TOX_ERR_FILE_SEND_FRIEND_NOT_CONNECTED, + + /** + * Filename length exceeded TOX_MAX_FILENAME_LENGTH bytes. + */ + TOX_ERR_FILE_SEND_NAME_TOO_LONG, + + /** + * Too many ongoing transfers. The maximum number of concurrent file transfers + * is 256 per friend per direction (sending and receiving). + */ + TOX_ERR_FILE_SEND_TOO_MANY, + +} TOX_ERR_FILE_SEND; + + +/** + * Send a file transmission request. + * + * Maximum filename length is TOX_MAX_FILENAME_LENGTH bytes. The filename + * should generally just be a file name, not a path with directory names. + * + * If a non-UINT64_MAX file size is provided, it can be used by both sides to + * determine the sending progress. File size can be set to UINT64_MAX for streaming + * data of unknown size. + * + * File transmission occurs in chunks, which are requested through the + * `file_chunk_request` event. + * + * When a friend goes offline, all file transfers associated with the friend are + * purged from core. + * + * If the file contents change during a transfer, the behaviour is unspecified + * in general. What will actually happen depends on the mode in which the file + * was modified and how the client determines the file size. + * + * - If the file size was increased + * - and sending mode was streaming (file_size = UINT64_MAX), the behaviour + * will be as expected. + * - and sending mode was file (file_size != UINT64_MAX), the + * file_chunk_request callback will receive length = 0 when Core thinks + * the file transfer has finished. If the client remembers the file size as + * it was when sending the request, it will terminate the transfer normally. + * If the client re-reads the size, it will think the friend cancelled the + * transfer. + * - If the file size was decreased + * - and sending mode was streaming, the behaviour is as expected. + * - and sending mode was file, the callback will return 0 at the new + * (earlier) end-of-file, signalling to the friend that the transfer was + * cancelled. + * - If the file contents were modified + * - at a position before the current read, the two files (local and remote) + * will differ after the transfer terminates. + * - at a position after the current read, the file transfer will succeed as + * expected. + * - In either case, both sides will regard the transfer as complete and + * successful. + * + * @param friend_number The friend number of the friend the file send request + * should be sent to. + * @param kind The meaning of the file to be sent. + * @param file_size Size in bytes of the file the client wants to send, UINT64_MAX if + * unknown or streaming. + * @param file_id A file identifier of length TOX_FILE_ID_LENGTH that can be used to + * uniquely identify file transfers across core restarts. If NULL, a random one will + * be generated by core. It can then be obtained by using tox_file_get_file_id(). + * @param filename Name of the file. Does not need to be the actual name. This + * name will be sent along with the file send request. + * @param filename_length Size in bytes of the filename. + * + * @return A file number used as an identifier in subsequent callbacks. This + * number is per friend. File numbers are reused after a transfer terminates. + * On failure, this function returns UINT32_MAX. Any pattern in file numbers + * should not be relied on. + */ +uint32_t tox_file_send(Tox *tox, uint32_t friend_number, uint32_t kind, uint64_t file_size, const uint8_t *file_id, + const uint8_t *filename, size_t filename_length, TOX_ERR_FILE_SEND *error); + +typedef enum TOX_ERR_FILE_SEND_CHUNK { + + /** + * The function returned successfully. + */ + TOX_ERR_FILE_SEND_CHUNK_OK, + + /** + * The length parameter was non-zero, but data was NULL. + */ + TOX_ERR_FILE_SEND_CHUNK_NULL, + + /** + * The friend_number passed did not designate a valid friend. + */ + TOX_ERR_FILE_SEND_CHUNK_FRIEND_NOT_FOUND, + + /** + * This client is currently not connected to the friend. + */ + TOX_ERR_FILE_SEND_CHUNK_FRIEND_NOT_CONNECTED, + + /** + * No file transfer with the given file number was found for the given friend. + */ + TOX_ERR_FILE_SEND_CHUNK_NOT_FOUND, + + /** + * File transfer was found but isn't in a transferring state: (paused, done, + * broken, etc...) (happens only when not called from the request chunk callback). + */ + TOX_ERR_FILE_SEND_CHUNK_NOT_TRANSFERRING, + + /** + * Attempted to send more or less data than requested. The requested data size is + * adjusted according to maximum transmission unit and the expected end of + * the file. Trying to send less or more than requested will return this error. + */ + TOX_ERR_FILE_SEND_CHUNK_INVALID_LENGTH, + + /** + * Packet queue is full. + */ + TOX_ERR_FILE_SEND_CHUNK_SENDQ, + + /** + * Position parameter was wrong. + */ + TOX_ERR_FILE_SEND_CHUNK_WRONG_POSITION, + +} TOX_ERR_FILE_SEND_CHUNK; + + +/** + * Send a chunk of file data to a friend. + * + * This function is called in response to the `file_chunk_request` callback. The + * length parameter should be equal to the one received though the callback. + * If it is zero, the transfer is assumed complete. For files with known size, + * Core will know that the transfer is complete after the last byte has been + * received, so it is not necessary (though not harmful) to send a zero-length + * chunk to terminate. For streams, core will know that the transfer is finished + * if a chunk with length less than the length requested in the callback is sent. + * + * @param friend_number The friend number of the receiving friend for this file. + * @param file_number The file transfer identifier returned by tox_file_send. + * @param position The file or stream position from which to continue reading. + * @return true on success. + */ +bool tox_file_send_chunk(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, const uint8_t *data, + size_t length, TOX_ERR_FILE_SEND_CHUNK *error); + +/** + * If the length parameter is 0, the file transfer is finished, and the client's + * resources associated with the file number should be released. After a call + * with zero length, the file number can be reused for future file transfers. + * + * If the requested position is not equal to the client's idea of the current + * file or stream position, it will need to seek. In case of read-once streams, + * the client should keep the last read chunk so that a seek back can be + * supported. A seek-back only ever needs to read from the last requested chunk. + * This happens when a chunk was requested, but the send failed. A seek-back + * request can occur an arbitrary number of times for any given chunk. + * + * In response to receiving this callback, the client should call the function + * `tox_file_send_chunk` with the requested chunk. If the number of bytes sent + * through that function is zero, the file transfer is assumed complete. A + * client must send the full length of data requested with this callback. + * + * @param friend_number The friend number of the receiving friend for this file. + * @param file_number The file transfer identifier returned by tox_file_send. + * @param position The file or stream position from which to continue reading. + * @param length The number of bytes requested for the current chunk. + */ +typedef void tox_file_chunk_request_cb(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, + size_t length, void *user_data); + + +/** + * Set the callback for the `file_chunk_request` event. Pass NULL to unset. + * + * This event is triggered when Core is ready to send more file data. + */ +void tox_callback_file_chunk_request(Tox *tox, tox_file_chunk_request_cb *callback); + + +/******************************************************************************* + * + * :: File transmission: receiving + * + ******************************************************************************/ + + + +/** + * The client should acquire resources to be associated with the file transfer. + * Incoming file transfers start in the PAUSED state. After this callback + * returns, a transfer can be rejected by sending a TOX_FILE_CONTROL_CANCEL + * control command before any other control commands. It can be accepted by + * sending TOX_FILE_CONTROL_RESUME. + * + * @param friend_number The friend number of the friend who is sending the file + * transfer request. + * @param file_number The friend-specific file number the data received is + * associated with. + * @param kind The meaning of the file to be sent. + * @param file_size Size in bytes of the file the client wants to send, + * UINT64_MAX if unknown or streaming. + * @param filename Name of the file. Does not need to be the actual name. This + * name will be sent along with the file send request. + * @param filename_length Size in bytes of the filename. + */ +typedef void tox_file_recv_cb(Tox *tox, uint32_t friend_number, uint32_t file_number, uint32_t kind, uint64_t file_size, + const uint8_t *filename, size_t filename_length, void *user_data); + + +/** + * Set the callback for the `file_recv` event. Pass NULL to unset. + * + * This event is triggered when a file transfer request is received. + */ +void tox_callback_file_recv(Tox *tox, tox_file_recv_cb *callback); + +/** + * When length is 0, the transfer is finished and the client should release the + * resources it acquired for the transfer. After a call with length = 0, the + * file number can be reused for new file transfers. + * + * If position is equal to file_size (received in the file_receive callback) + * when the transfer finishes, the file was received completely. Otherwise, if + * file_size was UINT64_MAX, streaming ended successfully when length is 0. + * + * @param friend_number The friend number of the friend who is sending the file. + * @param file_number The friend-specific file number the data received is + * associated with. + * @param position The file position of the first byte in data. + * @param data A byte array containing the received chunk. + * @param length The length of the received chunk. + */ +typedef void tox_file_recv_chunk_cb(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, + const uint8_t *data, size_t length, void *user_data); + + +/** + * Set the callback for the `file_recv_chunk` event. Pass NULL to unset. + * + * This event is first triggered when a file transfer request is received, and + * subsequently when a chunk of file data for an accepted request was received. + */ +void tox_callback_file_recv_chunk(Tox *tox, tox_file_recv_chunk_cb *callback); + + +/******************************************************************************* + * + * :: Conference management + * + ******************************************************************************/ + + + +/** + * Conference types for the conference_invite event. + */ +typedef enum TOX_CONFERENCE_TYPE { + + /** + * Text-only conferences that must be accepted with the tox_conference_join function. + */ + TOX_CONFERENCE_TYPE_TEXT, + + /** + * Video conference. The function to accept these is in toxav. + */ + TOX_CONFERENCE_TYPE_AV, + +} TOX_CONFERENCE_TYPE; + + +/** + * The invitation will remain valid until the inviting friend goes offline + * or exits the conference. + * + * @param friend_number The friend who invited us. + * @param type The conference type (text only or audio/video). + * @param cookie A piece of data of variable length required to join the + * conference. + * @param length The length of the cookie. + */ +typedef void tox_conference_invite_cb(Tox *tox, uint32_t friend_number, TOX_CONFERENCE_TYPE type, const uint8_t *cookie, + size_t length, void *user_data); + + +/** + * Set the callback for the `conference_invite` event. Pass NULL to unset. + * + * This event is triggered when the client is invited to join a conference. + */ +void tox_callback_conference_invite(Tox *tox, tox_conference_invite_cb *callback); + +/** + * @param conference_number The conference number of the conference the message is intended for. + * @param peer_number The ID of the peer who sent the message. + * @param type The type of message (normal, action, ...). + * @param message The message data. + * @param length The length of the message. + */ +typedef void tox_conference_message_cb(Tox *tox, uint32_t conference_number, uint32_t peer_number, + TOX_MESSAGE_TYPE type, const uint8_t *message, size_t length, void *user_data); + + +/** + * Set the callback for the `conference_message` event. Pass NULL to unset. + * + * This event is triggered when the client receives a conference message. + */ +void tox_callback_conference_message(Tox *tox, tox_conference_message_cb *callback); + +/** + * @param conference_number The conference number of the conference the title change is intended for. + * @param peer_number The ID of the peer who changed the title. + * @param title The title data. + * @param length The title length. + */ +typedef void tox_conference_title_cb(Tox *tox, uint32_t conference_number, uint32_t peer_number, const uint8_t *title, + size_t length, void *user_data); + + +/** + * Set the callback for the `conference_title` event. Pass NULL to unset. + * + * This event is triggered when a peer changes the conference title. + * + * If peer_number == UINT32_MAX, then author is unknown (e.g. initial joining the conference). + */ +void tox_callback_conference_title(Tox *tox, tox_conference_title_cb *callback); + +/** + * Peer list state change types. + */ +typedef enum TOX_CONFERENCE_STATE_CHANGE { + + /** + * A peer has joined the conference. + */ + TOX_CONFERENCE_STATE_CHANGE_PEER_JOIN, + + /** + * A peer has exited the conference. + */ + TOX_CONFERENCE_STATE_CHANGE_PEER_EXIT, + + /** + * A peer has changed their name. + */ + TOX_CONFERENCE_STATE_CHANGE_PEER_NAME_CHANGE, + +} TOX_CONFERENCE_STATE_CHANGE; + + +/** + * @param conference_number The conference number of the conference the title change is intended for. + * @param peer_number The ID of the peer who changed the title. + * @param change The type of change (one of TOX_CONFERENCE_STATE_CHANGE). + */ +typedef void tox_conference_namelist_change_cb(Tox *tox, uint32_t conference_number, uint32_t peer_number, + TOX_CONFERENCE_STATE_CHANGE change, void *user_data); + + +/** + * Set the callback for the `conference_namelist_change` event. Pass NULL to unset. + * + * This event is triggered when the peer list changes (name change, peer join, peer exit). + */ +void tox_callback_conference_namelist_change(Tox *tox, tox_conference_namelist_change_cb *callback); + +typedef enum TOX_ERR_CONFERENCE_NEW { + + /** + * The function returned successfully. + */ + TOX_ERR_CONFERENCE_NEW_OK, + + /** + * The conference instance failed to initialize. + */ + TOX_ERR_CONFERENCE_NEW_INIT, + +} TOX_ERR_CONFERENCE_NEW; + + +/** + * Creates a new conference. + * + * This function creates a new text conference. + * + * @return conference number on success, or UINT32_MAX on failure. + */ +uint32_t tox_conference_new(Tox *tox, TOX_ERR_CONFERENCE_NEW *error); + +typedef enum TOX_ERR_CONFERENCE_DELETE { + + /** + * The function returned successfully. + */ + TOX_ERR_CONFERENCE_DELETE_OK, + + /** + * The conference number passed did not designate a valid conference. + */ + TOX_ERR_CONFERENCE_DELETE_CONFERENCE_NOT_FOUND, + +} TOX_ERR_CONFERENCE_DELETE; + + +/** + * This function deletes a conference. + * + * @param conference_number The conference number of the conference to be deleted. + * + * @return true on success. + */ +bool tox_conference_delete(Tox *tox, uint32_t conference_number, TOX_ERR_CONFERENCE_DELETE *error); + +/** + * Error codes for peer info queries. + */ +typedef enum TOX_ERR_CONFERENCE_PEER_QUERY { + + /** + * The function returned successfully. + */ + TOX_ERR_CONFERENCE_PEER_QUERY_OK, + + /** + * The conference number passed did not designate a valid conference. + */ + TOX_ERR_CONFERENCE_PEER_QUERY_CONFERENCE_NOT_FOUND, + + /** + * The peer number passed did not designate a valid peer. + */ + TOX_ERR_CONFERENCE_PEER_QUERY_PEER_NOT_FOUND, + + /** + * The client is not connected to the conference. + */ + TOX_ERR_CONFERENCE_PEER_QUERY_NO_CONNECTION, + +} TOX_ERR_CONFERENCE_PEER_QUERY; + + +/** + * Return the number of peers in the conference. Return value is unspecified on failure. + */ +uint32_t tox_conference_peer_count(const Tox *tox, uint32_t conference_number, TOX_ERR_CONFERENCE_PEER_QUERY *error); + +/** + * Return the length of the peer's name. Return value is unspecified on failure. + */ +size_t tox_conference_peer_get_name_size(const Tox *tox, uint32_t conference_number, uint32_t peer_number, + TOX_ERR_CONFERENCE_PEER_QUERY *error); + +/** + * Copy the name of peer_number who is in conference_number to name. + * name must be at least TOX_MAX_NAME_LENGTH long. + * + * @return true on success. + */ +bool tox_conference_peer_get_name(const Tox *tox, uint32_t conference_number, uint32_t peer_number, uint8_t *name, + TOX_ERR_CONFERENCE_PEER_QUERY *error); + +/** + * Copy the public key of peer_number who is in conference_number to public_key. + * public_key must be TOX_PUBLIC_KEY_SIZE long. + * + * @return true on success. + */ +bool tox_conference_peer_get_public_key(const Tox *tox, uint32_t conference_number, uint32_t peer_number, + uint8_t *public_key, TOX_ERR_CONFERENCE_PEER_QUERY *error); + +/** + * Return true if passed peer_number corresponds to our own. + */ +bool tox_conference_peer_number_is_ours(const Tox *tox, uint32_t conference_number, uint32_t peer_number, + TOX_ERR_CONFERENCE_PEER_QUERY *error); + +typedef enum TOX_ERR_CONFERENCE_INVITE { + + /** + * The function returned successfully. + */ + TOX_ERR_CONFERENCE_INVITE_OK, + + /** + * The conference number passed did not designate a valid conference. + */ + TOX_ERR_CONFERENCE_INVITE_CONFERENCE_NOT_FOUND, + + /** + * The invite packet failed to send. + */ + TOX_ERR_CONFERENCE_INVITE_FAIL_SEND, + +} TOX_ERR_CONFERENCE_INVITE; + + +/** + * Invites a friend to a conference. + * + * @param friend_number The friend number of the friend we want to invite. + * @param conference_number The conference number of the conference we want to invite the friend to. + * + * @return true on success. + */ +bool tox_conference_invite(Tox *tox, uint32_t friend_number, uint32_t conference_number, + TOX_ERR_CONFERENCE_INVITE *error); + +typedef enum TOX_ERR_CONFERENCE_JOIN { + + /** + * The function returned successfully. + */ + TOX_ERR_CONFERENCE_JOIN_OK, + + /** + * The cookie passed has an invalid length. + */ + TOX_ERR_CONFERENCE_JOIN_INVALID_LENGTH, + + /** + * The conference is not the expected type. This indicates an invalid cookie. + */ + TOX_ERR_CONFERENCE_JOIN_WRONG_TYPE, + + /** + * The friend number passed does not designate a valid friend. + */ + TOX_ERR_CONFERENCE_JOIN_FRIEND_NOT_FOUND, + + /** + * Client is already in this conference. + */ + TOX_ERR_CONFERENCE_JOIN_DUPLICATE, + + /** + * Conference instance failed to initialize. + */ + TOX_ERR_CONFERENCE_JOIN_INIT_FAIL, + + /** + * The join packet failed to send. + */ + TOX_ERR_CONFERENCE_JOIN_FAIL_SEND, + +} TOX_ERR_CONFERENCE_JOIN; + + +/** + * Joins a conference that the client has been invited to. + * + * @param friend_number The friend number of the friend who sent the invite. + * @param cookie Received via the `conference_invite` event. + * @param length The size of cookie. + * + * @return conference number on success, UINT32_MAX on failure. + */ +uint32_t tox_conference_join(Tox *tox, uint32_t friend_number, const uint8_t *cookie, size_t length, + TOX_ERR_CONFERENCE_JOIN *error); + +typedef enum TOX_ERR_CONFERENCE_SEND_MESSAGE { + + /** + * The function returned successfully. + */ + TOX_ERR_CONFERENCE_SEND_MESSAGE_OK, + + /** + * The conference number passed did not designate a valid conference. + */ + TOX_ERR_CONFERENCE_SEND_MESSAGE_CONFERENCE_NOT_FOUND, + + /** + * The message is too long. + */ + TOX_ERR_CONFERENCE_SEND_MESSAGE_TOO_LONG, + + /** + * The client is not connected to the conference. + */ + TOX_ERR_CONFERENCE_SEND_MESSAGE_NO_CONNECTION, + + /** + * The message packet failed to send. + */ + TOX_ERR_CONFERENCE_SEND_MESSAGE_FAIL_SEND, + +} TOX_ERR_CONFERENCE_SEND_MESSAGE; + + +/** + * Send a text chat message to the conference. + * + * This function creates a conference message packet and pushes it into the send + * queue. + * + * The message length may not exceed TOX_MAX_MESSAGE_LENGTH. Larger messages + * must be split by the client and sent as separate messages. Other clients can + * then reassemble the fragments. + * + * @param conference_number The conference number of the conference the message is intended for. + * @param type Message type (normal, action, ...). + * @param message A non-NULL pointer to the first element of a byte array + * containing the message text. + * @param length Length of the message to be sent. + * + * @return true on success. + */ +bool tox_conference_send_message(Tox *tox, uint32_t conference_number, TOX_MESSAGE_TYPE type, const uint8_t *message, + size_t length, TOX_ERR_CONFERENCE_SEND_MESSAGE *error); + +typedef enum TOX_ERR_CONFERENCE_TITLE { + + /** + * The function returned successfully. + */ + TOX_ERR_CONFERENCE_TITLE_OK, + + /** + * The conference number passed did not designate a valid conference. + */ + TOX_ERR_CONFERENCE_TITLE_CONFERENCE_NOT_FOUND, + + /** + * The title is too long or empty. + */ + TOX_ERR_CONFERENCE_TITLE_INVALID_LENGTH, + + /** + * The title packet failed to send. + */ + TOX_ERR_CONFERENCE_TITLE_FAIL_SEND, + +} TOX_ERR_CONFERENCE_TITLE; + + +/** + * Return the length of the conference title. Return value is unspecified on failure. + * + * The return value is equal to the `length` argument received by the last + * `conference_title` callback. + */ +size_t tox_conference_get_title_size(const Tox *tox, uint32_t conference_number, TOX_ERR_CONFERENCE_TITLE *error); + +/** + * Write the title designated by the given conference number to a byte array. + * + * Call tox_conference_get_title_size to determine the allocation size for the `title` parameter. + * + * The data written to `title` is equal to the data received by the last + * `conference_title` callback. + * + * @param title A valid memory region large enough to store the title. + * If this parameter is NULL, this function has no effect. + * + * @return true on success. + */ +bool tox_conference_get_title(const Tox *tox, uint32_t conference_number, uint8_t *title, + TOX_ERR_CONFERENCE_TITLE *error); + +/** + * Set the conference title and broadcast it to the rest of the conference. + * + * Title length cannot be longer than TOX_MAX_NAME_LENGTH. + * + * @return true on success. + */ +bool tox_conference_set_title(Tox *tox, uint32_t conference_number, const uint8_t *title, size_t length, + TOX_ERR_CONFERENCE_TITLE *error); + +/** + * Return the number of conferences in the Tox instance. + * This should be used to determine how much memory to allocate for `tox_conference_get_chatlist`. + */ +size_t tox_conference_get_chatlist_size(const Tox *tox); + +/** + * Copy a list of valid conference IDs into the array chatlist. Determine how much space + * to allocate for the array with the `tox_conference_get_chatlist_size` function. + */ +void tox_conference_get_chatlist(const Tox *tox, uint32_t *chatlist); + +/** + * Returns the type of conference (TOX_CONFERENCE_TYPE) that conference_number is. Return value is + * unspecified on failure. + */ +typedef enum TOX_ERR_CONFERENCE_GET_TYPE { + + /** + * The function returned successfully. + */ + TOX_ERR_CONFERENCE_GET_TYPE_OK, + + /** + * The conference number passed did not designate a valid conference. + */ + TOX_ERR_CONFERENCE_GET_TYPE_CONFERENCE_NOT_FOUND, + +} TOX_ERR_CONFERENCE_GET_TYPE; + + +TOX_CONFERENCE_TYPE tox_conference_get_type(const Tox *tox, uint32_t conference_number, + TOX_ERR_CONFERENCE_GET_TYPE *error); + + +/******************************************************************************* + * + * :: Low-level custom packet sending and receiving + * + ******************************************************************************/ + + + +typedef enum TOX_ERR_FRIEND_CUSTOM_PACKET { + + /** + * The function returned successfully. + */ + TOX_ERR_FRIEND_CUSTOM_PACKET_OK, + + /** + * One of the arguments to the function was NULL when it was not expected. + */ + TOX_ERR_FRIEND_CUSTOM_PACKET_NULL, + + /** + * The friend number did not designate a valid friend. + */ + TOX_ERR_FRIEND_CUSTOM_PACKET_FRIEND_NOT_FOUND, + + /** + * This client is currently not connected to the friend. + */ + TOX_ERR_FRIEND_CUSTOM_PACKET_FRIEND_NOT_CONNECTED, + + /** + * The first byte of data was not in the specified range for the packet type. + * This range is 200-254 for lossy, and 160-191 for lossless packets. + */ + TOX_ERR_FRIEND_CUSTOM_PACKET_INVALID, + + /** + * Attempted to send an empty packet. + */ + TOX_ERR_FRIEND_CUSTOM_PACKET_EMPTY, + + /** + * Packet data length exceeded TOX_MAX_CUSTOM_PACKET_SIZE. + */ + TOX_ERR_FRIEND_CUSTOM_PACKET_TOO_LONG, + + /** + * Packet queue is full. + */ + TOX_ERR_FRIEND_CUSTOM_PACKET_SENDQ, + +} TOX_ERR_FRIEND_CUSTOM_PACKET; + + +/** + * Send a custom lossy packet to a friend. + * + * The first byte of data must be in the range 200-254. Maximum length of a + * custom packet is TOX_MAX_CUSTOM_PACKET_SIZE. + * + * Lossy packets behave like UDP packets, meaning they might never reach the + * other side or might arrive more than once (if someone is messing with the + * connection) or might arrive in the wrong order. + * + * Unless latency is an issue, it is recommended that you use lossless custom + * packets instead. + * + * @param friend_number The friend number of the friend this lossy packet + * should be sent to. + * @param data A byte array containing the packet data. + * @param length The length of the packet data byte array. + * + * @return true on success. + */ +bool tox_friend_send_lossy_packet(Tox *tox, uint32_t friend_number, const uint8_t *data, size_t length, + TOX_ERR_FRIEND_CUSTOM_PACKET *error); + +/** + * Send a custom lossless packet to a friend. + * + * The first byte of data must be in the range 160-191. Maximum length of a + * custom packet is TOX_MAX_CUSTOM_PACKET_SIZE. + * + * Lossless packet behaviour is comparable to TCP (reliability, arrive in order) + * but with packets instead of a stream. + * + * @param friend_number The friend number of the friend this lossless packet + * should be sent to. + * @param data A byte array containing the packet data. + * @param length The length of the packet data byte array. + * + * @return true on success. + */ +bool tox_friend_send_lossless_packet(Tox *tox, uint32_t friend_number, const uint8_t *data, size_t length, + TOX_ERR_FRIEND_CUSTOM_PACKET *error); + +/** + * @param friend_number The friend number of the friend who sent a lossy packet. + * @param data A byte array containing the received packet data. + * @param length The length of the packet data byte array. + */ +typedef void tox_friend_lossy_packet_cb(Tox *tox, uint32_t friend_number, const uint8_t *data, size_t length, + void *user_data); + + +/** + * Set the callback for the `friend_lossy_packet` event. Pass NULL to unset. + * + */ +void tox_callback_friend_lossy_packet(Tox *tox, tox_friend_lossy_packet_cb *callback); + +/** + * @param friend_number The friend number of the friend who sent the packet. + * @param data A byte array containing the received packet data. + * @param length The length of the packet data byte array. + */ +typedef void tox_friend_lossless_packet_cb(Tox *tox, uint32_t friend_number, const uint8_t *data, size_t length, + void *user_data); + + +/** + * Set the callback for the `friend_lossless_packet` event. Pass NULL to unset. + * + */ +void tox_callback_friend_lossless_packet(Tox *tox, tox_friend_lossless_packet_cb *callback); + + +/******************************************************************************* + * + * :: Low-level network information + * + ******************************************************************************/ + + + +/** + * Writes the temporary DHT public key of this instance to a byte array. + * + * This can be used in combination with an externally accessible IP address and + * the bound port (from tox_self_get_udp_port) to run a temporary bootstrap node. + * + * Be aware that every time a new instance is created, the DHT public key + * changes, meaning this cannot be used to run a permanent bootstrap node. + * + * @param dht_id A memory region of at least TOX_PUBLIC_KEY_SIZE bytes. If this + * parameter is NULL, this function has no effect. + */ +void tox_self_get_dht_id(const Tox *tox, uint8_t *dht_id); + +typedef enum TOX_ERR_GET_PORT { + + /** + * The function returned successfully. + */ + TOX_ERR_GET_PORT_OK, + + /** + * The instance was not bound to any port. + */ + TOX_ERR_GET_PORT_NOT_BOUND, + +} TOX_ERR_GET_PORT; + + +/** + * Return the UDP port this Tox instance is bound to. + */ +uint16_t tox_self_get_udp_port(const Tox *tox, TOX_ERR_GET_PORT *error); + +/** + * Return the TCP port this Tox instance is bound to. This is only relevant if + * the instance is acting as a TCP relay. + */ +uint16_t tox_self_get_tcp_port(const Tox *tox, TOX_ERR_GET_PORT *error); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libs/libtox/src/toxcore/tox_api.c b/libs/libtox/src/toxcore/tox_api.c new file mode 100644 index 0000000000..b6c8c38618 --- /dev/null +++ b/libs/libtox/src/toxcore/tox_api.c @@ -0,0 +1,97 @@ +#include "tox.h" + +#include <stdlib.h> +#include <string.h> + +#define SET_ERROR_PARAMETER(param, x) {if(param) {*param = x;}} + + +#define CONST_FUNCTION(lowercase, uppercase) \ +uint32_t tox_##lowercase(void) \ +{ \ + return TOX_##uppercase; \ +} + +CONST_FUNCTION(version_major, VERSION_MAJOR) +CONST_FUNCTION(version_minor, VERSION_MINOR) +CONST_FUNCTION(version_patch, VERSION_PATCH) +CONST_FUNCTION(public_key_size, PUBLIC_KEY_SIZE) +CONST_FUNCTION(secret_key_size, SECRET_KEY_SIZE) +CONST_FUNCTION(address_size, ADDRESS_SIZE) +CONST_FUNCTION(max_name_length, MAX_NAME_LENGTH) +CONST_FUNCTION(max_status_message_length, MAX_STATUS_MESSAGE_LENGTH) +CONST_FUNCTION(max_friend_request_length, MAX_FRIEND_REQUEST_LENGTH) +CONST_FUNCTION(max_message_length, MAX_MESSAGE_LENGTH) +CONST_FUNCTION(max_custom_packet_size, MAX_CUSTOM_PACKET_SIZE) +CONST_FUNCTION(hash_length, HASH_LENGTH) +CONST_FUNCTION(file_id_length, FILE_ID_LENGTH) +CONST_FUNCTION(max_filename_length, MAX_FILENAME_LENGTH) + + +#define ACCESSORS(type, ns, name) \ +type tox_options_get_##ns##name(const struct Tox_Options *options) \ +{ \ + return options->ns##name; \ +} \ +void tox_options_set_##ns##name(struct Tox_Options *options, type name) \ +{ \ + options->ns##name = name; \ +} + +ACCESSORS(bool, , ipv6_enabled) +ACCESSORS(bool, , udp_enabled) +ACCESSORS(TOX_PROXY_TYPE, proxy_ , type) +ACCESSORS(const char *, proxy_ , host) +ACCESSORS(uint16_t, proxy_ , port) +ACCESSORS(uint16_t, , start_port) +ACCESSORS(uint16_t, , end_port) +ACCESSORS(uint16_t, , tcp_port) +ACCESSORS(bool, , hole_punching_enabled) +ACCESSORS(TOX_SAVEDATA_TYPE, savedata_, type) +ACCESSORS(size_t, savedata_, length) +ACCESSORS(tox_log_cb *, log_, callback) +ACCESSORS(void *, log_, user_data) +ACCESSORS(bool, , local_discovery_enabled) + +const uint8_t *tox_options_get_savedata_data(const struct Tox_Options *options) +{ + return options->savedata_data; +} + +void tox_options_set_savedata_data(struct Tox_Options *options, const uint8_t *data, size_t length) +{ + options->savedata_data = data; + options->savedata_length = length; +} + +void tox_options_default(struct Tox_Options *options) +{ + if (options) { + struct Tox_Options default_options = { 0 }; + *options = default_options; + tox_options_set_ipv6_enabled(options, true); + tox_options_set_udp_enabled(options, true); + tox_options_set_proxy_type(options, TOX_PROXY_TYPE_NONE); + tox_options_set_hole_punching_enabled(options, true); + tox_options_set_local_discovery_enabled(options, true); + } +} + +struct Tox_Options *tox_options_new(TOX_ERR_OPTIONS_NEW *error) +{ + struct Tox_Options *options = (struct Tox_Options *)malloc(sizeof(struct Tox_Options)); + + if (options) { + tox_options_default(options); + SET_ERROR_PARAMETER(error, TOX_ERR_OPTIONS_NEW_OK); + return options; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_OPTIONS_NEW_MALLOC); + return NULL; +} + +void tox_options_free(struct Tox_Options *options) +{ + free(options); +} diff --git a/libs/libtox/src/toxcore/util.c b/libs/libtox/src/toxcore/util.c new file mode 100644 index 0000000000..5202598518 --- /dev/null +++ b/libs/libtox/src/toxcore/util.c @@ -0,0 +1,194 @@ +/* + * Utilities. + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2013 Tox project. + * Copyright © 2013 plutooo + * + * This file is part of Tox, the free peer to peer instant messenger. + * This file is donated to the Tox Project. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define _XOPEN_SOURCE 600 + +#include "util.h" + +#include "crypto_core.h" /* for CRYPTO_PUBLIC_KEY_SIZE */ +#include "network.h" /* for current_time_monotonic */ + +#include <time.h> + + +/* don't call into system billions of times for no reason */ +static uint64_t unix_time_value; +static uint64_t unix_base_time_value; + +/* XXX: note that this is not thread-safe; if multiple threads call unix_time_update() concurrently, the return value of + * unix_time() may fail to increase monotonically with increasing time */ +void unix_time_update(void) +{ + if (unix_base_time_value == 0) { + unix_base_time_value = ((uint64_t)time(NULL) - (current_time_monotonic() / 1000ULL)); + } + + unix_time_value = (current_time_monotonic() / 1000ULL) + unix_base_time_value; +} + +uint64_t unix_time(void) +{ + return unix_time_value; +} + +int is_timeout(uint64_t timestamp, uint64_t timeout) +{ + return timestamp + timeout <= unix_time(); +} + + +/* id functions */ +bool id_equal(const uint8_t *dest, const uint8_t *src) +{ + return public_key_cmp(dest, src) == 0; +} + +uint32_t id_copy(uint8_t *dest, const uint8_t *src) +{ + memcpy(dest, src, CRYPTO_PUBLIC_KEY_SIZE); + return CRYPTO_PUBLIC_KEY_SIZE; +} + +void host_to_net(uint8_t *num, uint16_t numbytes) +{ +#ifndef WORDS_BIGENDIAN + uint32_t i; + VLA(uint8_t, buff, numbytes); + + for (i = 0; i < numbytes; ++i) { + buff[i] = num[numbytes - i - 1]; + } + + memcpy(num, buff, numbytes); +#endif +} + +uint16_t lendian_to_host16(uint16_t lendian) +{ +#ifdef WORDS_BIGENDIAN + return (lendian << 8) | (lendian >> 8); +#else + return lendian; +#endif +} + +void host_to_lendian32(uint8_t *dest, uint32_t num) +{ +#ifdef WORDS_BIGENDIAN + num = ((num << 8) & 0xFF00FF00) | ((num >> 8) & 0xFF00FF); + num = (num << 16) | (num >> 16); +#endif + memcpy(dest, &num, sizeof(uint32_t)); +} + +void lendian_to_host32(uint32_t *dest, const uint8_t *lendian) +{ + uint32_t d; + memcpy(&d, lendian, sizeof(uint32_t)); +#ifdef WORDS_BIGENDIAN + d = ((d << 8) & 0xFF00FF00) | ((d >> 8) & 0xFF00FF); + d = (d << 16) | (d >> 16); +#endif + *dest = d; +} + +/* state load/save */ +int load_state(load_state_callback_func load_state_callback, Logger *log, void *outer, + const uint8_t *data, uint32_t length, uint16_t cookie_inner) +{ + if (!load_state_callback || !data) { + LOGGER_ERROR(log, "load_state() called with invalid args.\n"); + return -1; + } + + + uint16_t type; + uint32_t length_sub, cookie_type; + uint32_t size_head = sizeof(uint32_t) * 2; + + while (length >= size_head) { + lendian_to_host32(&length_sub, data); + lendian_to_host32(&cookie_type, data + sizeof(length_sub)); + data += size_head; + length -= size_head; + + if (length < length_sub) { + /* file truncated */ + LOGGER_ERROR(log, "state file too short: %u < %u\n", length, length_sub); + return -1; + } + + if (lendian_to_host16((cookie_type >> 16)) != cookie_inner) { + /* something is not matching up in a bad way, give up */ + LOGGER_ERROR(log, "state file garbled: %04x != %04x\n", (cookie_type >> 16), cookie_inner); + return -1; + } + + type = lendian_to_host16(cookie_type & 0xFFFF); + + int ret = load_state_callback(outer, data, length_sub, type); + + if (ret == -1) { + return -1; + } + + /* -2 means end of save. */ + if (ret == -2) { + return 0; + } + + data += length_sub; + length -= length_sub; + } + + return length == 0 ? 0 : -1; +} + +int create_recursive_mutex(pthread_mutex_t *mutex) +{ + pthread_mutexattr_t attr; + + if (pthread_mutexattr_init(&attr) != 0) { + return -1; + } + + if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) != 0) { + pthread_mutexattr_destroy(&attr); + return -1; + } + + if (pthread_mutex_init(mutex, &attr) != 0) { + pthread_mutexattr_destroy(&attr); + return -1; + } + + pthread_mutexattr_destroy(&attr); + + return 0; +} diff --git a/libs/libtox/src/toxcore/util.h b/libs/libtox/src/toxcore/util.h new file mode 100644 index 0000000000..6ef9a75f8c --- /dev/null +++ b/libs/libtox/src/toxcore/util.h @@ -0,0 +1,64 @@ +/* + * Utilities. + */ + +/* + * Copyright © 2016-2017 The TokTok team. + * Copyright © 2013 Tox project. + * Copyright © 2013 plutooo + * + * This file is part of Tox, the free peer to peer instant messenger. + * This file is donated to the Tox Project. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + */ +#ifndef UTIL_H +#define UTIL_H + +#include <pthread.h> +#include <stdbool.h> +#include <stdint.h> + +#include "logger.h" + +#define MIN(a,b) (((a)<(b))?(a):(b)) +#define PAIR(TYPE1__, TYPE2__) struct { TYPE1__ first; TYPE2__ second; } + +void unix_time_update(void); +uint64_t unix_time(void); +int is_timeout(uint64_t timestamp, uint64_t timeout); + + +/* id functions */ +bool id_equal(const uint8_t *dest, const uint8_t *src); +uint32_t id_copy(uint8_t *dest, const uint8_t *src); /* return value is CLIENT_ID_SIZE */ + +void host_to_net(uint8_t *num, uint16_t numbytes); +#define net_to_host(x, y) host_to_net(x, y) + +uint16_t lendian_to_host16(uint16_t lendian); +#define host_tolendian16(x) lendian_to_host16(x) + +void host_to_lendian32(uint8_t *dest, uint32_t num); +void lendian_to_host32(uint32_t *dest, const uint8_t *lendian); + +/* state load/save */ +typedef int (*load_state_callback_func)(void *outer, const uint8_t *data, uint32_t len, uint16_t type); +int load_state(load_state_callback_func load_state_callback, Logger *log, void *outer, + const uint8_t *data, uint32_t length, uint16_t cookie_inner); + +/* Returns -1 if failed or 0 if success */ +//int create_recursive_mutex(pthread_mutex_t *mutex); + +#endif /* UTIL_H */ |