/* TCP_connection.c * * Handles TCP relay connections between two Tox clients. * * Copyright (C) 2015 Tox project All Rights Reserved. * * This file is part of Tox. * * 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 . * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "TCP_connection.h" /* Set the size of the array to num. * * return -1 if realloc fails. * return 0 if it succeeds. */ #define realloc_tox_array(array, element_size, num, temp_pointer) (num ? (temp_pointer = realloc(array, num * element_size), 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, sizeof(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, sizeof(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, sizeof(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, sizeof(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, uint8_t *packet, uint16_t length) { TCP_Connection_to *con_to = get_connection(tcp_c, connections_number); if (!con_to) { return -1; } unsigned int i; int ret = -1; for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) { uint32_t tcp_con_num = con_to->connections[i].tcp_connection; //TODO if (tcp_con_num) { tcp_con_num -= 1; TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_con_num); if (!tcp_con) { continue; } // ret = send_data(c->tcp_connections[tcp_index], conn->con_number_tcp[tcp_index], packet, length); } } return ret; } 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 *object) { tcp_c->tcp_data_callback = tcp_data_callback; tcp_c->tcp_data_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 (memcmp(con_to->public_key, public_key, crypto_box_PUBLICKEYBYTES) == 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 (memcmp(tcp_con->connection->public_key, relay_pk, crypto_box_PUBLICKEYBYTES) == 0) { return i; } } } return -1; } /* Create a new TCP connection to public_key. * * 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_box_PUBLICKEYBYTES); 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; //TODO return wipe_connection(tcp_c, connections_number); } static _Bool tcp_connection_in_conn(TCP_Connection_to *con_to, 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, 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, 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 index on success. * return -1 on failure. */ static int set_tcp_connection_status(TCP_Connection_to *con_to, 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)) { 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); } } kill_TCP_connection(tcp_con->connection); return wipe_tcp_connection(tcp_c, tcp_connections_number); } /* 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 (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 = object; TCP_Connections *tcp_c = 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; set_tcp_connection_number(tcp_con->connection, connection_id, connections_number); 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; 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 = object; TCP_Connections *tcp_c = 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; } else if (status == 2) { if (set_tcp_connection_status(con_to, tcp_connections_number, TCP_CONNECTIONS_STATUS_ONLINE, connection_id) == -1) return -1; } return 0; } static int tcp_data_callback(void *object, uint32_t number, uint8_t connection_id, const uint8_t *data, uint16_t length) { if (length == 0) return -1; TCP_Client_Connection *TCP_client_con = object; TCP_Connections *tcp_c = 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); return 0; } static int tcp_oob_callback(void *object, const uint8_t *public_key, const uint8_t *data, uint16_t length) { if (length == 0) return -1; TCP_Client_Connection *TCP_client_con = object; TCP_Connections *tcp_c = 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; uint8_t relay_pk[crypto_box_PUBLICKEYBYTES]; memcpy(relay_pk, tcp_con->connection->public_key, crypto_box_PUBLICKEYBYTES); if (tcp_c->tcp_oob_callback) tcp_c->tcp_oob_callback(tcp_c->tcp_oob_callback_object, public_key, relay_pk, data, length); return 0; } static int tcp_onion_callback(void *object, const uint8_t *data, uint16_t length) { TCP_Connections *tcp_c = object; if (tcp_c->tcp_onion_callback) tcp_c->tcp_onion_callback(tcp_c->tcp_onion_callback_object, data, length); 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_data_callback, con); oob_data_handler(con, &tcp_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; 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)) { send_tcp_relay_routing_request(tcp_c, tcp_connections_number, con_to->public_key); } } } tcp_relay_set_callbacks(tcp_c, tcp_connections_number); tcp_con->status = TCP_CONN_CONNECTED; return 0; } static int add_tcp_relay(TCP_Connections *tcp_c, IP_Port ip_port, const uint8_t *relay_pk) { 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->dht->self_public_key, tcp_c->dht->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 tied to a connection. * * 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) { if (add_tcp_connection_to_conn(con_to, tcp_connections_number) == -1) return -1; TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); if (!tcp_con) return -1; ++tcp_con->lock_count; if (tcp_con->status == TCP_CONN_CONNECTED) { send_tcp_relay_routing_request(tcp_c, tcp_connections_number, con_to->public_key); } return 0; } else { int tcp_connections_number = add_tcp_relay(tcp_c, ip_port, relay_pk); if (add_tcp_connection_to_conn(con_to, tcp_connections_number) == -1) { return -1; } TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); if (!tcp_con) return -1; ++tcp_con->lock_count; return 0; } } TCP_Connections *new_tcp_connections(DHT *dht) { if (dht == NULL) return NULL; TCP_Connections *temp = calloc(1, sizeof(TCP_Connections)); if (temp == NULL) return NULL; temp->dht = dht; return temp; } static void do_tcp_conns(TCP_Connections *tcp_c) { 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) { do_TCP_connection(tcp_con->connection); if (tcp_con->connection->status == TCP_CLIENT_DISCONNECTED) { 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); } } } } void do_tcp_connections(TCP_Connections *tcp_c) { //TODO kill unused conns do_tcp_conns(tcp_c); } void kill_tcp_connections(TCP_Connections *tcp_c) { //TODO free(tcp_c); }