diff --git a/src/server/tabletennis.c b/src/server/tabletennis.c new file mode 100644 --- /dev/null +++ b/src/server/tabletennis.c @@ -0,0 +1,234 @@ +/* + * Copyright 2010 Nathan Phillip Brink + * + * This file is a part of DistRen. + * + * DistRen is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DistRen 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DistRen. If not, see . + */ + +#include "distrend.h" +#include "listen.h" +#include "tabletennis.h" + +#include "common/request.h" +#include "common/protocol.h" + +#include +#include +#include +#include + +struct tabletennis +{ + unsigned int ping_interval; + unsigned int pong_time; + + /* of type (struct distrend_client *) */ + queue_t clients_to_ping; + + /* of type (struct distrend_client *) */ + queue_t clients_need_pong; + + struct timespec time_last_check; +}; + +static int tabletennis_pong_request_handle(struct general_info *geninfo, struct distrend_client *client, size_t req_len, void *req_data); +static int tabletennis_ping_request_handle(struct general_info *geninfo, struct distrend_client *client, size_t req_len, void *req_data); + +tabletennis_t tabletennis_new(struct distrend_listens *listens, unsigned int ping_interval, unsigned int pong_time) +{ + tabletennis_t tabletennis; + + tabletennis = malloc(sizeof(struct tabletennis)); + + tabletennis->ping_interval = ping_interval; + tabletennis->pong_time = pong_time; + tabletennis->clients_to_ping = q_init(); + tabletennis->clients_need_pong = q_init(); + clock_gettime(CLOCK_MONOTONIC, &tabletennis->time_last_check); + + distrend_listen_handler_add(listens, DISTREN_REQUEST_PING, &tabletennis_ping_request_handle); + distrend_listen_handler_add(listens, DISTREN_REQUEST_PONG, &tabletennis_pong_request_handle); + + return tabletennis; +} + +int tabletennis_add_client(tabletennis_t tabletennis, struct distrend_client *client) +{ + client->tabletennis_client.state = TABLETENNIS_NEED_PING; + client->tabletennis_client.time_next_check = tabletennis->time_last_check.tv_sec + + tabletennis->ping_interval; + + q_enqueue(tabletennis->clients_to_ping, client, 0); + + return 0; +} + +int tabletennis_serve(tabletennis_t tabletennis) +{ + struct timespec time_now; + struct distrend_client *client; + time_t time_next_check; + + struct distren_request *req; + void *req_data; + size_t req_len; + + clock_gettime(CLOCK_MONOTONIC, &time_now); + + time_next_check = time_now.tv_sec + tabletennis->pong_time; + for(client = q_front(tabletennis->clients_to_ping); + client && client->tabletennis_client.time_next_check < time_now.tv_sec; + client = q_front(tabletennis->clients_to_ping)) + { + q_dequeue(tabletennis->clients_to_ping); + + /* use time_next_check as the ping data */ + req_len = distren_request_poing(&req, &req_data, 1, &time_next_check, sizeof(time_next_check)); + distrend_client_write_request(client, req, req_data); + distren_request_free_with_data(req, req_data); + + client->tabletennis_client.state = TABLETENNIS_NEED_PONG; + + client->tabletennis_client.time_next_check = time_next_check; + + q_enqueue(tabletennis->clients_need_pong, client, 0); + } + + time_next_check = time_now.tv_sec + tabletennis->ping_interval; + for(client = q_front(tabletennis->clients_need_pong); + client && client->tabletennis_client.time_next_check < time_now.tv_sec; + client = q_front(tabletennis->clients_need_pong)) + { + q_dequeue(tabletennis->clients_need_pong); + + if(client->tabletennis_client.state == TABLETENNIS_NEED_PONG) + { + distrend_send_disconnect(client, "You failed to respond to a PING packet after some seconds!"); + client->tabletennis_client.state = TABLETENNIS_DELINQUENT; + } + else /* state must be TABLETENNIS_HAS_PONG */ + { + client->tabletennis_client.time_next_check = time_next_check; + client->tabletennis_client.state = TABLETENNIS_NEED_PING; + + q_enqueue(tabletennis->clients_to_ping, client, 0); + } + } + + /* for tabletennis_add_client() */ + memcpy(&tabletennis->time_last_check, &time_now, sizeof(struct timespec)); + + return 0; +} + +/** + * helper for tabletennis_del_client() which just looks for a + * particular pointer in the queue (a.k.a., list). + */ +static int tabletennis_del_client_traverse(void *trav_data, void *client) +{ + if(trav_data == client) + return FALSE; + + return TRUE; +} + +int tabletennis_del_client(tabletennis_t tabletennis, struct distrend_client *client) +{ + list_t thelist; + + thelist = NULL; + switch(client->tabletennis_client.state) + { + case TABLETENNIS_NEED_PING: + thelist = tabletennis->clients_to_ping; + break; + + case TABLETENNIS_NEED_PONG: + case TABLETENNIS_HAS_PONG: + thelist = tabletennis->clients_need_pong; + break; + + case TABLETENNIS_DELINQUENT: + thelist = NULL; + break; + } + + if(!thelist) + return 0; + + list_traverse(thelist, (void *)client, (list_traverse_func_t)&tabletennis_del_client_traverse, + LIST_FORW | LIST_FRNT | LIST_SAVE); + if(list_curr(thelist) == client) + list_remove_curr(thelist); + + return 0; +} + +void tabletennis_free(tabletennis_t tabletennis) +{ + q_free(tabletennis->clients_to_ping, LIST_NODEALLOC); + q_free(tabletennis->clients_need_pong, LIST_NODEALLOC); + + free(tabletennis); +} + +/* implementations of locals */ + +/** + * Handles a DISTREN_REQUEST_PING from a client. + * + * @todo throttling? + */ +static int tabletennis_ping_request_handle(struct general_info *geninfo, struct distrend_client *client, size_t req_len, void *req_data) +{ + struct distren_request *pong_req; + + if(req_len > 32) + distrend_send_disconnect(client, "You have tried to send a PING packet with a length longer than 32 bytes."); + + /** + respond to the client using the data he sent in his PONG + command. + */ + distren_request_new(&pong_req, req_len, DISTREN_REQUEST_PONG); + distrend_client_write_request(client, pong_req, req_data); + distren_request_free(pong_req); + + return 0; +} + +static int tabletennis_pong_request_handle(struct general_info *geninfo, struct distrend_client *client, size_t req_len, void *req_data) +{ + fprintf(stderr, "got pong\n"); + + /* + * The only place that sends PINGs from distrend is + * tabletennis_serve() and it uses + * client->tabletennis_client.time_next_check as the cookie. + */ + if(client->tabletennis_client.state == TABLETENNIS_NEED_PONG + && req_len == sizeof(client->tabletennis_client.time_next_check) + && !memcmp(req_data, &client->tabletennis_client.time_next_check, sizeof(client->tabletennis_client.time_next_check))) + { + /* valid match */ + client->tabletennis_client.state = TABLETENNIS_HAS_PONG; + + return 0; + } + /* no match or invalid state */ + distrend_send_disconnect(client, "You tried to send a PONG packet without first receiving a PING packet."); + return 0; +}