/*
 * Marko Kiiskila carnil@cs.tut.fi 
 * 
 * Copyright (c) 1996
 * Tampere University of Technology - Telecommunications Laboratory
 * All rights reserved.
 *
 * Permission to use, copy, modify and distribute this
 * software and its documentation is hereby granted,
 * provided that both the copyright notice and this
 * permission notice appear in all copies of the software,
 * derivative works or modified versions, and any portions
 * thereof, that both notices appear in supporting
 * documentation, and that the use of this software is
 * acknowledged in any publications resulting from using
 * the software.
 * 
 * TUT ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
 * CONDITION AND DISCLAIMS ANY LIABILITY OF ANY KIND FOR
 * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS
 * SOFTWARE.
 * 
 */
/*
 *
 * Initialization, main loop
 * 
 * $Id: main.c,v 1.19 1996/08/06 14:14:11 carnil Exp carnil $
 */

/* Global includes */
#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <getopt.h>

#include <atm.h>

/* Digital includes */
#include "codes.h"
#include "g_types.h"
#include "lec.h"
#include "af_lane.h"
#include "utl.h"
#include "lec_ctrl.h"
#include "utl_os.h"
#include "g_event.h"
#include "addr_reg.h"
#include "le_disp.h"
#include "cm.h"

/* Local incs */
#include "conn.h"
#include "timers.h"
#include "kernel_itf.h"

/* LD_ELAN_CONTEXT
 *  Context block for a single ELAN membership.  An instance of this structure
 *  is created each time the LEC_DATA module registers a new ELAN membership.
 *
 *  p_next.................Next pointer that allows this structure to be
 *                         stored in a simple list.
 *
 *  lec_handle.............The handle of this LEC module instance.  This is
 *                         the handle that was generated by the lec_create
 *                         function.
 *
 *  client_context.........The context supplied by the ELAN client during
 *                         lec registration.  There is one context for each
 *                         ELAN.
 *
 *
 *  lc_elan_handle.........Handle for the ELAN instance of the LEC_CTRL module.
 *
 *  sap_handle.............Handle of the registered DATA SAP.
 *
 *  lan_type...............LAN Type of the joined (or joining) ELAN.
 *
 *  direct_conn_context....Connection Context for data direct VCCs.
 *
 *  bus_conn_context.......Connection Context for BUS VCCs.
 *
 *  lec_id.................LEC_ID of this LEC in the ELAN.  Used in the LAN
 *                         emulation header for data packets.
 *
 *  lec_event_callback.....Callback for ELAN events.
 *
 *  lec_rcv_callback.......Callback for received packets.
 *
 *  lec_xmt_done_callback..Callback for transmit done indications.
 */

typedef struct _ld_elan {
  struct _ld_elan        *p_next;
  char                   *p_text;
  HANDLE                  lec_handle;
  HANDLE                  client_context;
  HANDLE                  lc_elan_handle;
  HANDLE                  sap_handle;
  LAN_TYPE                lan_type;
  LEC_CONN_CONTEXT        direct_conn_context;
  LEC_CONN_CONTEXT        mcast_conn_context;
  LEC_CONN_CONTEXT        ctrl_conn_context;
  UINT16                  lec_id;
  LEC_EVENT_CALLBACK      lec_event_callback;
  LEC_RCV_CALLBACK        lec_rcv_callback;
  LEC_XMT_DONE_CALLBACK   lec_xmt_done_callback;
} LD_ELAN_CONTEXT;

/*
 * Declare a simple list type for ELAN Contexts.
 */
SIMPLE_LIST (LD_ELAN_CONTEXT, ELAN_LIST);

/* LD_CONTEXT
 *  Context structure for LEC instances.  There is one such instance for each
 *  physical port that provides LAN Emulation Client capabilities.
 *
 *  lc_handle..LEC_CTRL handle for this physical port.
 *
 *  elan_list..List of ELAN contexts that are registered to this physical port.
 */
typedef struct {
  HANDLE      lc_handle;
  HANDLE      addr_reg_handle;
  ELAN_LIST   elan_list;
} LD_CONTEXT;

int stay_alive =1;
int reset=0;

#define EMOD   MOD_LEC_DATA
#define EINST  "main.c"

/* Mask for messages, include emask.h */
UINT32 EMASK = (EM_SERR | EM_NERR | EM_WARN | EM_MSG);

/* Protos */
void lec_event_callback(HANDLE context, LEC_EVENT event,
			LAN_MTU mtu, char *p_elan_name);
static void elan_status_callback(HANDLE elan_handle, BOOLEAN elan_available,
				 LAN_MTU lan_mtu, char *p_elan_name,
				 UINT16 lec_id);
void lec_rcv_callback(HANDLE context, UINT32 length,
		      void  **pp_pkt);
void lec_xmt_done_callback(HANDLE context, void *p_packet);
static void sig_die(int a);
static void usage(const char *progname);
static int esi_convert(char *parsestring, unsigned char *mac_addr);

static void
sig_die(int a)
{
  stay_alive = 0;
  reset = 1;
}

void
sig_reset(int a)
{
  reset = 1;  
}

static void 
elan_status_callback(HANDLE elan_handle, BOOLEAN elan_available,
		     LAN_MTU lan_mtu, char *p_elan_name,
		     UINT16 lec_id)
{
  LD_ELAN_CONTEXT *elan;

  EVENT(EM_DEBUG,("elan_status_callback\tavailable:%d\n",elan_available));

  elan = (LD_ELAN_CONTEXT *)elan_handle;
  elan->lec_id = lec_id;
  kernel_sendmsg(l_set_lecid, NULL, NULL, NULL, lec_id);
}

void
lec_event_callback(HANDLE context, LEC_EVENT event,
		   LAN_MTU mtu, char *p_elan_name)
{
  EVENT(EM_DEBUG,("lec_event_callback\t %s: %s\n",
		  event==LEC_NETWORK_AVAILABLE?"LEC_NETWORK_AVAILABLE":"LEC_NETWORK_UNAVAILABLE",p_elan_name));
}

void 
lec_rcv_callback(HANDLE context, UINT32 length,
		 void  **pp_pkt)
{
  return;
}

void 
lec_xmt_done_callback(HANDLE context, void *p_packet)
{
  EVENT(EM_DEBUG,("lec_xmt_done_callback\n"));
}

/*++
* ========================
* = ld_xmt_done_callback =
* ========================
*
* Overview:
*
* Arguments:
*   conn_context
*   p_packet
*   user_data
*              
* Returns:
*   None
*
* Preconditions:
*   None
*
* Postconditions:
*   The elan_up state variable takes on the value of elan_available.
--*/

static void 
ld_xmt_done_callback(HANDLE conn_context, void *p_packet,
		     UINT32 user_data)
{
  LEC_CONN_CONTEXT *p_conn;
  LD_ELAN_CONTEXT  *p_elan;

  p_conn = (LEC_CONN_CONTEXT *) conn_context;
  p_elan = (LD_ELAN_CONTEXT  *) p_conn->ld_elan_handle;
  
  if (user_data) {
    fprintf(stdout, ("Transmit-Done Indication on Internal Data\n"));
  }
  
  p_elan->lec_xmt_done_callback(p_elan->client_context, p_packet);
  
  return;
}

static void 
ld_rcv_callback(HANDLE conn_context, HANDLE conn_handle, UINT32 length, 
		UINT32 user_data, AAL_DATA *p_aal_data, void **pp_packet)
{
  /* 
   * We should get only control messages here. 
   */
  lc_sap_rcv (conn_context,
	      conn_handle,
	      length,
	      user_data,
	      p_aal_data,
	      pp_packet);
  return;
}

static void
usage(const char *progname)
{
  printf("Usage: %s [-c LECS_address]|[-s LES_address] [-e esi] [-n VLAN_name]"
    " [-m mesg_mask] [-l listen_address] [-i interface_number]"
    " [-q qos_spec]\n",progname);
}

static int
esi_convert(char *parsestring, unsigned char *mac_addr)
{
  const char *hexchars = "abcdefABCDEF0123456789";
  char hexnum [17+1], curr;
  int i = 0, j = -1, hexindex = 0, tmp;
  char *k, *string;

  if (strchr(parsestring,'.') ||     /* do we have separators like */
      strchr(parsestring,':')) {     /* 00:20:22:23:04:05 */
    k = parsestring;
    for (i = 0; i < strlen(parsestring); i++) {
      curr = *k;
      if (curr == ':' || curr == '.') {   /* separator ? */
        if (i - j == 3) {  /* there were 2 hex characters */
          ;
        }
        else if (i - j == 2) {  /* there was only 1 hex char */
          hexnum [hexindex] = hexnum [hexindex-1];
          hexnum [hexindex-1] = '0';
          hexindex +=1;
        }
        else   /* too many hexchars in a byte */
          return -1;
        j = i;  /* j is the location of the last separator */
      }
      else if (strchr(hexchars, curr) == NULL) /* not a hexchar ? */
        return -1;
      else {  /* we have a hex character */
        hexnum [hexindex] = curr;
        hexindex +=1;
      }
      k++;
    }
    hexnum [hexindex] = '\0';
    string = hexnum;
  }
  else {   /* no separators */
    k = parsestring;
    while (*k != '\0') {
      if (strchr(hexchars, *k) == NULL)
	return -1;
      k++;
    }

    string = parsestring;
  }

  /* the esi now looks like 002022230405 */
  i = strlen(string);
  if (i != 12)
    return -1;
  for(i=0;i<6;i++) {
    sscanf(&string[i*2],"%2x",&tmp);
    mac_addr[i]=(unsigned char)tmp;
  }
  return 0;
}

STATUS 
main(int argc, char **argv)
{
  HANDLE line_up_handle = NULL;
  HANDLE callback_context = NULL;
  HANDLE addr_reg_handle = NULL;
  int esi_set=0;
  ESI mac_addr;
  LAN_TYPE lan_type = LAN_802_3;
  LAN_MTU max_frame_size = MTU_1516;
  char p_elan_name[32] = { 00 };
  INIT_METHOD init_method = INIT_WELL_KNOWN_LECS;
  int atm_set=0;
  ADDR_ATM manual_atm_addr;
  BOOLEAN proxy_flag = FALSE;
  HANDLE lport_handle = NULL;
  char p_text[] = "Lec Control Unit";
  
  LD_CONTEXT *p_context;
  LD_ELAN_CONTEXT *p_elan;
  STATUS status;

  fd_set fds;
  fd_set efds;
  int poll_ret =0;
  struct timeval *timeout;
  HANDLE current_timer;
  ADDR_ATM *listen_address = NULL;
  int itf_num = 0;
  const char *qos_spec = NULL;

  while(poll_ret != -1) {
    poll_ret = getopt(argc, argv, "c:e:n:s:m:l:i:q:");
    switch(poll_ret) {
    case 'c':
      if (atm_set) {
	usage(argv[0]);
	exit(-1);
      }
      if (address_convert(optarg, &manual_atm_addr)<0) {
	EVENT(EM_SERR,("Invalid LECS address\n"));
	usage(argv[0]);
	exit(-1);
      } 
      EVENT(EM_MSG,("LECS address: %s\n",disp_atm_text(manual_atm_addr)));
      init_method = INIT_MANUAL_LECS;
      atm_set=1;
      break;
    case 'e':
      if(esi_convert(optarg, mac_addr)<0) {
	EVENT(EM_SERR,("Invalid ESI format\n"));
	usage(argv[0]);
	exit(-1);
      }
      EVENT(EM_MSG,("LEC ESI:%s\n",disp_esi_text(mac_addr)));
      esi_set=1;
      break;
    case 'n':
      memcpy(p_elan_name, optarg, strlen(optarg));
      p_elan_name[strlen(optarg)] = '\0';
      EVENT(EM_MSG,("Vlan name :'%s'\n", p_elan_name));
      break;
    case 's':
      if (atm_set) {
	usage(argv[0]);
	exit(-1);
      }
      if (address_convert(optarg, &manual_atm_addr)<0) {
	EVENT(EM_SERR,("Invalid LES address\n"));
	usage(argv[0]);
	exit(-1);
      }
      EVENT(EM_MSG,("LES address: %s\n", disp_atm_text(manual_atm_addr)));
      init_method = INIT_MANUAL_LES;
      atm_set=1;
      break;
    case 'm':
      if (sscanf(optarg, "%lx",&EMASK)<0) {
	EVENT(EM_SERR,("Invalid mask value"));
	exit(-1);
      }
      EVENT(EM_MSG,("Message mask set to %lx\n",EMASK));
      break;
    case 'l':
      listen_address = (ADDR_ATM*)mem_alloc(EINST, sizeof(ADDR_ATM));
      if (address_convert(optarg, listen_address)<0) {
	EVENT(EM_SERR,("Invalid ATM listen address\n"));
	usage(argv[0]);
	exit(-1);
      }
      EVENT(EM_MSG,("Our ATM address: %s\n", disp_atm_text(*listen_address)));
      break;
    case 'i':
      if (sscanf(optarg, "%d",&itf_num)<0 || itf_num >= MAX_LEC_ITF) {
	EVENT(EM_SERR,("Invalid interface number\n"));
	usage(argv[0]);
	exit(-1);
      }
      EVENT(EM_MSG,("Interface number set to %d\n",itf_num));
      break;
    case 'q':
      if (text2qos(optarg,NULL,0) < 0) {
	EVENT(EM_SERR,("Invalid QOS specification\n"));
	usage(argv[0]);
	exit(-1);
      }
      qos_spec = optarg;
      break;
    case -1:
      break;
    default:
      usage(argv[0]);
      exit(-1);
    }
  }
  if (!esi_set) {
    if(addr_getesi(mac_addr)<0) {
      EVENT(EM_SERR,("Can't get ESI from kernel!\n"));
      return STATUS_K_HW_FAULT;
    } 
    EVENT(EM_MSG,("LEC ESI:%s\n",disp_esi_text(mac_addr)));
  }
  while (stay_alive) {

    /* Reserve signals */
    signal(SIGHUP, sig_reset);
    signal(SIGINT, sig_die);
    signal(SIGQUIT, sig_die);
    signal(SIGABRT, sig_die);
    signal(SIGTERM, sig_die);
    signal(SIGSEGV, sig_die);
    
    /* Allocate storage for the data context.  Return failure status if memory
     * could not be allocated.
     */

    p_context = (LD_CONTEXT *) os_mem_alloc (sizeof (LD_CONTEXT));
    
    if (p_context == NULL)
      return STATUS_K_RESOURCES;
    
    /* Initialize the list of ELANs. */
    
    utl_list_init (p_context->elan_list);
    
    if ((itf_num=kernel_init(mac_addr, itf_num))<0) {
      EVENT(EM_SERR,("Kernel interface creation failed, exiting...\n"));
      return STATUS_K_RESOURCES;
    } 
    
    /* Create an instance of the LEC_CTRL module.  If there is an error, back
     * out of our previous operations and return failure status.
     */
    status = lc_create (line_up_handle,
			elan_status_callback,
			&p_context->lc_handle);
    if (status != STATUS_K_SUCCESS) {
      os_mem_dealloc (p_context);
      return STATUS_K_RESOURCES;
    }
        
    /* Allocate memory for a new ELAN context.  If the allocation fails, return
     * error status.
     */
    p_elan = (LD_ELAN_CONTEXT *)os_mem_alloc(sizeof (LD_ELAN_CONTEXT));
    if (p_elan == NULL) {
      os_mem_dealloc (p_context);
      return STATUS_K_RESOURCES;
    }
    memset(p_elan,0, sizeof(LD_ELAN_CONTEXT));
    
    p_elan->p_text     = p_text;
    
    /* Initialize the ELAN context's neighbor handles. */
    
    p_elan->lec_handle     = (HANDLE)p_context;
    p_elan->client_context = callback_context;
    p_elan->lan_type       = lan_type;
    
    /* Store the callback addresses in the ELAN context. */
    
    p_elan->lec_event_callback    = lec_event_callback;
    p_elan->lec_rcv_callback      = lec_rcv_callback;
    p_elan->lec_xmt_done_callback = lec_xmt_done_callback;
    
    /* Register with the LEC_CTRL module to cause it to allocate an
     * ELAN instance.  If there is an error, back out of our operations so
     * far and return error status.
     */
    status = lc_register (p_context->lc_handle,
			  (HANDLE) p_elan,
			  addr_reg_handle,
			  mac_addr,
			  lan_type,
			  max_frame_size,
			  p_elan_name,
			  init_method,
			  manual_atm_addr,
			  proxy_flag,
			  lport_handle,
			  p_text,
			  &p_elan->lc_elan_handle);
    if (status != STATUS_K_SUCCESS) {
      EVENT (EM_SERR, ("Failed to register lec_ctrl instance.\n"));
      os_mem_dealloc (p_elan);
      os_mem_dealloc (p_context);
      return STATUS_K_RESOURCES;
    }

    /* Pre-initialize the data-direct and BUS connection context blocks. */
    
    p_elan->direct_conn_context.ld_elan_handle = (HANDLE) p_elan;
    p_elan->direct_conn_context.lc_elan_handle = p_elan->lc_elan_handle;
    p_elan->direct_conn_context.conn_type      = LEC_DIRECT;
  
    p_elan->mcast_conn_context.ld_elan_handle  = (HANDLE) p_elan;
    p_elan->mcast_conn_context.lc_elan_handle  = p_elan->lc_elan_handle;
    p_elan->mcast_conn_context.conn_type       = LEC_MCAST;
    
    p_elan->ctrl_conn_context.ld_elan_handle   = (HANDLE) p_elan;
    p_elan->ctrl_conn_context.lc_elan_handle   = p_elan->lc_elan_handle;
    p_elan->ctrl_conn_context.conn_type        = LEC_CTRL;
    
    /* Add the ELAN context to the ELAN list in the module context. */
    
    utl_list_add (p_context->elan_list, p_elan);
  
    /* Register with the CM as a SAP.  This is the Data-SAP for this ELAN.
     * Again, back out if there is an error.
     */
    status = cm_sap_register(p_elan->lc_elan_handle,
			     ld_rcv_callback,
			     lc_sap_vc_notify,
			     lc_sap_connect,
			     ld_xmt_done_callback,
			     itf_num,
			     "LAN Emulation Convergence",
			     &p_elan->sap_handle);
    if (status != STATUS_K_SUCCESS) {
      EVENT(EM_SERR, ("Failed to register as an NSAP client.\n"));
      lc_deregister(p_elan->lc_elan_handle);
      os_mem_dealloc(p_elan);
      os_mem_dealloc(p_context);
      return STATUS_K_RESOURCES;
    }
    
    if (listen_address) {
      addr_set_atm_addr(listen_address);
    }

    /*
      Create listen sockets. LES / BUS will have different addresses.
      -> no clash.
      */
    if (conn_create_listensocket(p_elan->sap_handle,
				 p_elan->lc_elan_handle,
				 BLLI_CONTROL,
				 qos_spec,
				 NULL)<0) {
      EVENT(EM_NERR,("Creating Control blli codepoint failed\n"));
      return STATUS_K_ATM_RESOURCES;
    }
    if (conn_create_listensocket(p_elan->sap_handle,
				 p_elan->lc_elan_handle,
				 BLLI_BUS_802_3,
				 qos_spec,
				 NULL)<0) {
      EVENT(EM_NERR, ("Creating Bus 802_3 blli codepoint failed\n"));
      return STATUS_K_ATM_RESOURCES;
    }
    
    if (conn_create_listensocket(p_elan->sap_handle,
				 p_elan->lc_elan_handle,
				 BLLI_DIRECT_802_3,
				 qos_spec,
				 NULL)<0) {
      EVENT(EM_NERR, ("Creating Data direct 802_3 blli codepoint failed\n"));
      return STATUS_K_ATM_RESOURCES;
    }	  

    /* Registration is complete.  Notify the LEC_CTRL module that it may begin
     * the ELAN join.
     */
    lc_join_start (p_elan->lc_elan_handle,
		   p_elan->sap_handle,
		   (HANDLE) &p_elan->direct_conn_context,
		   (HANDLE) &p_elan->mcast_conn_context,
		   qos_spec,
		   (HANDLE) &p_elan->ctrl_conn_context);


    /* Notify about new connections */
    conn_call_callbacks();
    
    while(!reset && stay_alive) {
      EVENT(EM_DEBUG,("Checking for new event\n"));
      current_timer = timer_find_soonest();
      timeout = timer_get_expiration(current_timer);
      if (timeout) {
	EVENT(EM_DEBUG,("Sleeping %d secs....\n",timeout->tv_sec));
      } else {
	EVENT(EM_DEBUG,("No timers pending...\n"));
      }
      if (!reset && (!timeout || (timeout->tv_sec!=0 && timeout->tv_usec!=0))){
	FD_ZERO(&fds);
	conn_get_fds(&fds);
	FD_ZERO(&efds);
	conn_get_connecting_fds(&efds);
	poll_ret = select(FD_SETSIZE, &fds, &efds, &efds, timeout);
	EVENT(EM_DEBUG,("Selected .....\n\n\n"));
	if (poll_ret < 0) {
	  perror("select");
	} else if (poll_ret >0) {
	  EVENT(EM_DEBUG,("RESET:%d\n",reset));
	  if (conn_check_incoming(&fds)<0) {
	    reset=1;
	  }
	  conn_check_incoming(&efds);
	}
      } else
	poll_ret =0;
      if (poll_ret ==0 && current_timer) { /* Timeout */
	timer_call_callback(current_timer);
      }
      EVENT(EM_DEBUG,("About to handle callbacks\n"));
      conn_call_callbacks();
      EVENT(EM_DEBUG,("Callbacks handled\n"));
    }
    if (reset && stay_alive) {
      EVENT(EM_NERR,("Resetting...\n"));
      /* Deregister with the Connection Manager. */
      cm_sap_unregister(p_elan->sap_handle);

      sleep(6);
      /* Deregister the LEC_CTRL instance for this ELAN.  This will in turn
       * deregister the le-arp instance.
       */
      lc_deregister(p_elan->lc_elan_handle);

      /* Deallocate the ELAN context data block. */
      
      os_mem_dealloc(p_elan);
      
      /* Request the destruction of the LEC_CTRL module instance. */
      
      lc_destroy(p_context->lc_handle);
      
      /* Deallocate the memory for the LEC context data block. */
      
      os_mem_dealloc(p_context);

#if 0
      /* Now we should be able to restart from empty table? */
      mem_usage();
      timer_usage();
#endif
      reset = 0;
    }
  }
  return STATUS_K_SUCCESS; 
}

/*
 *
 * $Log: main.c,v $
 * Revision 1.19  1996/08/06 14:14:11  carnil
 * Reset handling
 *
 * Revision 1.18  1996/07/07 11:51:47  carnil
 * Global_msgmask
 *
 * Revision 1.17  1996/06/10 04:27:28  carnil
 * compiler warning fixes
 *
 * Revision 1.15  1996/05/13 08:53:27  carnil
 * *** empty log message ***
 *
 * Revision 1.14  1996/04/25 19:42:13  carnil
 * Copyright notice
 *
 * Revision 1.13  1996/04/19 06:37:49  carnil
 * options from command line, ELAN name, kernel_itf.c call order fixed
 *
 * Revision 1.12  1996/04/11 09:16:14  carnil
 * data direct listen
 *
 * Revision 1.11  1996/03/30 15:14:23  carnil
 * Configuration vcc callback before loop
 *
 * Revision 1.10  1996/03/22 11:26:23  carnil
 * *** empty log message ***
 *
 * Revision 1.9  1996/03/19 11:03:36  carnil
 * *** empty log message ***
 *
 * Revision 1.8  1996/03/17 21:23:41  carnil
 * kernel_init()
 *
 * Revision 1.7  1996/03/04 08:55:39  carnil
 * listen socket creation works
 * Communication to servers works
 *
 * Revision 1.6  1996/02/29 11:24:12  carnil
 * Main loop alteration
 *
 * Revision 1.5  1996/02/16 06:16:20  carnil
 * oshandle,cmhandle removed
 *
 * Revision 1.4  1996/02/06  11:23:41  carnil
 * Main loop functionality enhanced
 *
 * Revision 1.3  1996/01/30  15:20:49  carnil
 * Main loop
 *
 * Revision 1.2  1996/01/23  10:02:12  carnil
 * *** empty log message ***
 *
 * Revision 1.1  1996/01/22  12:59:50  carnil
 * Initial revision
 *
 *
 */
