/*
 * nb_sample.c -- Sample program using the non-blocking API
 *
 * Copyright (C) 2005, Ranch Networks, Inc.
 *
 * This program 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.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 * Author : Srivatsa Chivukula <srivatsa@ranchnetworks.com>
 *
 */

/*
 * The flow of the program is as follows:
 *  1. Initialize the library.
 *  2. Open two channels to the middlebox, which has NAT and Firewall capabilities.
 *     One channel is for the SIMCO communication, the other is to receive
 *     asynchronous notifications.
 *  3. Add both the channels to the list of connections, the library would be listening on.
 *  4. Register the callback functions to receive the parsed response for the SIMCO
 *     comminication.
 *  5. Register the callback function to receive notifications. This should be registered 
 *     only once for a channel.
 *  6. Send the session establishment (SE) request on the SIMCO comm. channel.
 *  7. Send the session authentication (SA) request on the SIMCO comm. channel. Synchronize (i.e.,
 *     wait till the corresponding callback is called.)
 *  8. Send the session establishment request on the notification channel.
 *  9. Send the session authentication request on the notification channel. Synchronize.
 * 10. Send the policy enable rule (PER) request. Synchronize.
 * 11. Send the policy lifetime change (PLC) request for the rule set in step 10.
 * 12. Send the policy reserve rule (PRR). Synchronize.
 * 13. Send the policy enable rule after reservation (PEA) for the rule reserved in step 12.
 * 14. Send the policy rule status (PRS) request for the rule set in steps 12 and 13.
 * 15. Send the policy rule list (PRL) request for all the rule set on the middlebox.
 * 16. Send the session terminate (ST) request on communication and notification channels.
 *
 * The callbacks will be invoked asynchronously, when the corresponding
 * responses are received.
 */

#include <stdio.h>
#include <string.h>
#include <pthread.h>

#include "simco_client.h"

int policy_id = -1;
static char *cert = "/home/srivatsa/certificate/server.pem";
pthread_mutex_t policy_id_mutex;
pthread_cond_t policy_id_cv;

int se_callback(int con, int status);
int sa_callback(int con, int status);
int st_callback(int con, int status);
int per_callback(int con, simcoPacket_t *sp);
int plc_callback(int con, simcoPacket_t *sp);
int prr_callback(int con, simcoPacket_t *sp);
int pea_callback(int con, simcoPacket_t *sp);
int prs_callback(int con, simcoPacket_t *sp);
int prl_callback(int con, int *rules, int nrules);

int notif_callback(simcoPacket_t *sp);

int main(int argc, char** argv)
{
  int con = -1, notif_con = -1;
  char *host = "192.1.1.191";
  int ret;
  int error;

  simcoPacket_t spacket;

  char *login = "root";
  char *password = "ranchroot";

  if (argc >= 3)
    {
      login = argv[1];
      password = argv[2];
    }

  if (!mc_init(cert)) {
    return 0;
  }
  
  /* Get the SIMCO communication channel */
  if ((con = mc_comm_connect(host)) == -1)
    {
      mc_close();
      return -1;
    }
  
  /* Get the SIMCO notification channel */
  if ((notif_con = mc_notif_connect(host)) == -1)
    {
      mc_close();
      return -1;
    }

  /* Add connections to pool */
  mc_add_con_to_pool(con);
  mc_add_con_to_pool(notif_con);

  /* Register communication callbacks */
  reg_se_callback(se_callback);
  reg_sa_callback(sa_callback);
  reg_st_callback(st_callback);
  reg_per_callback(per_callback);
  reg_plc_callback(plc_callback);
  reg_prr_callback(prr_callback);
  reg_pea_callback(pea_callback);
  reg_prs_callback(prs_callback);
  reg_prl_callback(prl_callback);

  /* Register notification callbacks */
  reg_notif_callback(notif_con, notif_callback);

  pthread_mutex_init(&policy_id_mutex, NULL);
  pthread_cond_init(&policy_id_cv, NULL);
  
  /* SE and SA request on communication channel */
  if ((ret = simco_se_nb(con)) != 0)
    {
      mc_close();
      return -1;
    }

  if ((ret = simco_sa_nb(con, login, password)) != 0)
    {
      mc_close();
      return -1;
    }

  /* Wait till SA response comes back,
   * so that the client is authenticated before sending other requests.
   */
  pthread_mutex_lock(&policy_id_mutex);
  pthread_cond_wait(&policy_id_cv, &policy_id_mutex);
  pthread_mutex_unlock(&policy_id_mutex);

  /* Check for response errors */
  if ( (error =  simco_get_error(con)) != 0 )
    {
      printf("SA callback: error = %d\n", error);
      goto end;
    }

  /* NE and NA requests on notification channel */
  if ((ret = simco_ne_nb(notif_con)) != 0)
    {
      simco_st_nb(con);
      mc_close();
      return -1;
    }

  if ((ret = simco_na_nb(notif_con, login, password)) != 0)
    {
      simco_st_nb(con);
      mc_close();
      return -1;
    }

  /* Wait till NA response comes back */
  pthread_mutex_lock(&policy_id_mutex);
  pthread_cond_wait(&policy_id_cv, &policy_id_mutex);
  pthread_mutex_unlock(&policy_id_mutex);

  /* Check for response errors */
  if ( (error =  simco_get_error(con)) != 0 )
    {
      printf("NA callback: error = %d\n", error);
      goto end;
    }

  /* PER request */
  memset((void*)&spacket, 0, sizeof(simcoPacket_t));
  strcpy(spacket.internal_ip, "192.2.1.1");
  strcpy(spacket.external_ip, "192.2.1.2");
  spacket.internal_port = 5;
  spacket.external_port = 6;
  spacket.lifetime = 60;

  if ((ret = simco_per_nb(con, &spacket)) != 0)
    {
      goto end;
    }

  memset((void*)&spacket, 0, sizeof(simcoPacket_t));

  /* Wait till signal is received from the callback function,
   * to get the right policy id assigned by the server for PER request.
   */
  pthread_mutex_lock(&policy_id_mutex);
  pthread_cond_wait(&policy_id_cv, &policy_id_mutex);
  spacket.ruleId = policy_id;
  pthread_mutex_unlock(&policy_id_mutex);

  /* Check for response errors */
  if ( (error =  simco_get_error(con)) != 0 )
    {
      printf("PER callback: error = %d\n", error);
      goto end;
    }

  /* PLC request */
  spacket.lifetime = 15;
  if ((ret = simco_plc_nb(con, &spacket)) != 0)
    {
      goto end;
    }

  /* PRR request */
  memset((void*)&spacket, 0, sizeof(simcoPacket_t));
  spacket.lifetime = 60;
  if ((ret = simco_prr_nb(con, &spacket)) != 0)
    {
      goto end;
    }

  memset((void*)&spacket, 0, sizeof(simcoPacket_t));

  /* Wait till signal is received from the callback function,
   * to get the right policy id assigned by the server for PRR request.
   */
  pthread_mutex_lock(&policy_id_mutex);
  pthread_cond_wait(&policy_id_cv, &policy_id_mutex);
  spacket.ruleId = policy_id;
  pthread_mutex_unlock(&policy_id_mutex);

  /* Check for response errors */
  if ( (error =  simco_get_error(con)) != 0 )
    {
      printf("PRR callback: error = %d\n", error);
      goto end;
    }

  /* PEA request */
  strcpy(spacket.internal_ip, "192.2.1.1");
  strcpy(spacket.external_ip, "192.2.1.2");
  spacket.internal_port = 7;
  spacket.external_port = 8;
  spacket.lifetime = 60;
  if ((ret = simco_pea_nb(con, &spacket)) != 0)
    {
      goto end;
    }

  /* PRS request */
  memset((void*)&spacket, 0, sizeof(simcoPacket_t));
  spacket.ruleId = policy_id;
  if ((ret = simco_prs_nb(con, &spacket)) != 0)
    {
      goto end;
    }

  /* PRL request */
  if ((ret = simco_prl_nb(con)) != 0)
    {
      goto end;
    }

 end:
  simco_st_nb(con);
  simco_nt_nb(notif_con);

  pthread_mutex_destroy(&policy_id_mutex);
  pthread_cond_destroy(&policy_id_cv);
  
  /* Clean up */
  mc_close();
  return ret;
}

int se_callback(int con, int status)
{
  fprintf(stderr, "=== Session Establish Callback ===\n");
  fprintf(stderr, "Status:%d\n", status);
  return 1;
}

int sa_callback(int con, int status)
{
  fprintf(stderr, "=== Session Authenticate Callback ===\n");
  fprintf(stderr, "Status:%d\n", status);
  pthread_mutex_lock(&policy_id_mutex);
  pthread_cond_signal(&policy_id_cv);
  pthread_mutex_unlock(&policy_id_mutex);
  return 1;
}

int st_callback(int con, int status)
{
  fprintf(stderr, "=== Session Terminate Callback ===\n");
  fprintf(stderr, "Status:%d\n", status);
  return 1;
}

int per_callback(int con, simcoPacket_t *sp)
{
  fprintf(stderr, "=== Policy Enable Rule Callback ===\n");
  fprintf(stderr, "RuleId:%d GroupId:%d Lifetime:%d Outside_IP: %s Outside_Port:%d Status: %d\n",
	  sp->ruleId, sp->groupId, sp->lifetime, sp->outside_ip, sp->outside_port, sp->status);
  pthread_mutex_lock(&policy_id_mutex);
  policy_id = sp->ruleId;
  /* send signal */
  pthread_cond_signal(&policy_id_cv);
  pthread_mutex_unlock(&policy_id_mutex);
  return 1;
}

int plc_callback(int con, simcoPacket_t *sp)
{
  fprintf(stderr, "=== Policy Rule Lifetime Change Callback ===\n");
  fprintf(stderr, "RuleId:%d Lifetime:%d Status: %d\n", sp->ruleId, sp->lifetime, sp->status);
  return 1;
}

int prr_callback(int con, simcoPacket_t *sp)
{
  fprintf(stderr, "=== Policy Reserve Rule Callback ===\n");
  fprintf(stderr, "RuleId:%d GroupId:%d Lifetime:%d Outside_IP: %s Outside_Port:%d Status: %d\n",
          sp->ruleId, sp->groupId, sp->lifetime, sp->outside_ip, sp->outside_port, sp->status);
  pthread_mutex_lock(&policy_id_mutex);
  policy_id = sp->ruleId;
  /* send signal */
  pthread_cond_signal(&policy_id_cv);
  pthread_mutex_unlock(&policy_id_mutex);
  return 1;
}

int pea_callback(int con, simcoPacket_t *sp)
{
  fprintf(stderr, "=== PER After Reservation Callback ===\n");
  fprintf(stderr, "RuleId:%d GroupId:%d Lifetime:%d Outside_IP: %s Outside_Port:%d Status: %d\n",
          sp->ruleId, sp->groupId, sp->lifetime, sp->outside_ip, sp->outside_port, sp->status);
  return 1;
}

int prs_callback(int con, simcoPacket_t *sp)
{
  fprintf(stderr, "=== Policy Rule Status Callback ===\n");
  fprintf(stderr, "RuleId:%d GroupId:%d Lifetime:%d Outside_IP: %s Outside_Port:%d Status: %d\n",
          sp->ruleId, sp->groupId, sp->lifetime, sp->outside_ip, sp->outside_port, sp->status);
  return 1;
}

int notif_callback(simcoPacket_t *sp)
{
  fprintf(stderr, "=== Notification Callback ===\n");
  fprintf(stderr, "RuleId:%d Lifetime:%d Status: %d\n", sp->ruleId, sp->lifetime, sp->status);
  return 1;
}

int prl_callback(int con, int *rules, int nrules)
{
  int i;
  fprintf(stderr, "=== Policy Rule List Callback ===\n");
  printf("Number of rules returned: %d\n", nrules);
  for (i = 0; i < nrules; i++)
    printf("Rule Id: %d\n", rules[i]);
  return 1;
}
