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;
+}