Files @ da79b5082151
Branch filter:

Location: DistRen/src/server/tabletennis.c

binki
Extend the distren CLI a little bit more and specify some more of the protocol so that the client can be further worked on while cleaning up nonfunctional distrend.
/*
 * 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 <http://www.gnu.org/licenses/>.
 */

#include "common/config.h"

#include "distrend.h"
#include "listen.h"
#include "tabletennis.h"

#include "common/request.h"
#include "common/protocol.h"

#include <queue.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

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;

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