/*
* 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 "common/config.h"
#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, struct distren_request *req, void *req_data);
static int tabletennis_ping_request_handle(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, 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, (uint8_t)DISTREND_CLIENT_GOOD);
distrend_listen_handler_add(listens, DISTREN_REQUEST_PONG, &tabletennis_pong_request_handle, (uint8_t)DISTREND_CLIENT_GOOD);
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;
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 */
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_ALTR);
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, struct distren_request *req, 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, struct distren_request *req, 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;
}