/* conn.c - functions for handling SVCs, create, accept, send, etc. */

/*
 * 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.
 * 
 */

/* Copyright (C) 1999 Heikki Vatiainen hessu@cs.tut.fi */

#if HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>

#include <atm.h>
#include <atmsap.h>
#include <linux/atmlec.h>

#include <atmd.h>

#include "conn.h"
#include "display.h"
#include "lec.h"
#include "frames.h"
#include "kernel.h"

#define COMPONENT "conn.c"

/* status */
#define CONNECTED  42    /* Operational socket */
#define CONNECTING 43    /* Non-blocking socket, not yet connected */

/* type */
#define WEMADE 7
#define THEYMADE 8
#define LISTENING 9
#define KERNEL_SOCK 10

static Conn_t *connlist = NULL;

/* Local protos */
static void list_remove_conn(Conn_t *conn);
static Conn_t *list_add_conn(unsigned char *dest_atm_addr);
static Conn_t *conn_already_exists(unsigned char *atm_addr, Conn_t *current);
static const char *get_type_string(int type);
static int maxmtu2maxsdu(uint8_t mtu);
static uint16_t conn_type2codepoint(int conn_type);

static void delete_addr(unsigned char *atm_addr)
{
        struct atmlec_msg msg;

        msg.type = l_addr_delete;
        memcpy(msg.content.normal.atm_addr, atm_addr, ATM_ESA_LEN);
        msg_to_kernel(&msg, sizeof(struct atmlec_msg));

        return;
}

/* Checks if connection to atm_addr already exists. Does not
 * check against current though.
 * Returns NULL for no connection, or Conn_t of existing connection.
 */
static Conn_t *conn_already_exists(unsigned char *atm_addr, Conn_t *current)
{
        Conn_t *conn;

        conn = connlist;
        while (conn) {
                if (conn != current &&
                    conn->type != LISTENING && 
                    conn->type != KERNEL_SOCK) {
                        if (memcmp(conn->atm_address, atm_addr, ATM_ESA_LEN) == 0)
                                return conn;
                }
                conn = conn->next;
        }
        
        return NULL;
}

/* Initializes and fills in *sap and *qos according to Blli
 * code point value specified in conn_type.
 */
void init_conn_params(struct atm_sap *sap, struct atm_qos *qos,
                      uint16_t conn_type)
{

        unsigned int bllicode;
        int i, sdu;
        char qos_text[MAX_ATM_QOS_LEN + 1];

        diag(COMPONENT, DIAG_DEBUG, "init_conn_params, conn_type %x\n", conn_type);

        memset(qos, 0, sizeof(struct atm_qos));
        memset(sap, 0, sizeof(struct atm_sap));
        qos->aal = ATM_AAL5;
  
        /* Set the forward and backward Max CPCS-SDU Size */
        switch(conn_type) {
        case CONTROL_CONN:
                qos->rxtp.max_sdu = 1516;
                qos->txtp.max_sdu = 1516;
                break;
        case DATA_DIRECT_CONN:
        case MCAST_CONN:
                sdu = maxmtu2maxsdu(lec_params.c3_max_frame_size);
                qos->rxtp.max_sdu = sdu;
                qos->txtp.max_sdu = sdu;
                break;
        default:
                diag(COMPONENT, DIAG_ERROR, "unknown conn_type %x\n", conn_type);
                break;
        }
        
        /* ATM User Cell Rate/ATM Traffic Descriptor. */
        qos->txtp.traffic_class = ATM_UBR;
        qos->rxtp.traffic_class = ATM_UBR;
   
        if (get_verbosity(COMPONENT) >= DIAG_DEBUG) {
                if (qos2text(qos_text, sizeof(qos_text), qos, 0) < 0)
                        sprintf(qos_text, "<bad qos>");
                diag(COMPONENT, DIAG_DEBUG, "init_conn_params, QoS '%s'\n", qos_text);
        }

        /* No Broadband High Layer Information in LANE. */
        sap->bhli.hl_type = ATM_HL_NONE;

        /* Broadband Lower Layer Information. */
        sap->blli[0].l3_proto = ATM_L3_TR9577;
        sap->blli[0].l3.tr9577.ipi = NLPID_IEEE802_1_SNAP;
        sap->blli[0].l3.tr9577.snap[0] = 0x00;
        sap->blli[0].l3.tr9577.snap[1] = 0xa0;
        sap->blli[0].l3.tr9577.snap[2] = 0x3e;
        bllicode = conn_type2codepoint(conn_type);
        sap->blli[0].l3.tr9577.snap[3] = (unsigned char)(0xff&(bllicode>>8));
        sap->blli[0].l3.tr9577.snap[4] = (unsigned char)(0xff&bllicode);

        if (get_verbosity(COMPONENT) == DIAG_DEBUG) {
                for(i=0; i < 5; i++) {
                        diag(COMPONENT, DIAG_DEBUG, "snap[%d] = 0x%2.2x",
                             i, sap->blli[0].l3.tr9577.snap[i]);
                }
        }

        return;
}

/* Returns != 0 if blli indicates Data Direct
 * connection
 */
static int is_data_direct(struct atm_blli *blli)
{
        return (blli->l3.tr9577.snap[4] == DATA_DIRECT_8023 ||
                blli->l3.tr9577.snap[4] == DATA_DIRECT_8025);
}

/* Creates a socket with the specified parameters.
 * If listen_addr is non NULL binds to it.
 * Returns < 0 for error or new socket descriptor.
 */
static int get_socket(struct sockaddr_atmsvc *listen_addr,
                      struct atm_sap *sap, struct atm_qos *qos)
{
        int s, ret;

        s = socket(PF_ATMSVC, SOCK_DGRAM, 0);
        if (s < 0) {
                diag(COMPONENT, DIAG_ERROR, "socket creation failure: %s\n",
                     strerror(errno));
                return -1;
        }
        diag(COMPONENT, DIAG_DEBUG, "get_socket: got fd %d\n", s);
        
        if (setsockopt(s, SOL_ATM, SO_ATMQOS, qos, sizeof(struct atm_qos)) < 0) {
                diag(COMPONENT, DIAG_ERROR, "get_socket: setsockopt SO_ATMQOS: %s\n", strerror(errno));
                close(s);
                return -1;
        }
        if (setsockopt(s, SOL_ATM, SO_ATMSAP, sap, sizeof(struct atm_sap)) < 0) {
                diag(COMPONENT, DIAG_ERROR, "setup_svc setsockop(SO_ATMSAP)\n");
                close(s);
                return -1;
        }
        
        /* Bind the socket to our local address */
        if (listen_addr == NULL)
                return s;
        ret = bind(s, (struct sockaddr *)listen_addr, sizeof(struct sockaddr_atmsvc));
        if (ret < 0) {
                diag(COMPONENT, DIAG_ERROR, "bind error: %s\n", strerror(errno));
                close(s);
                return -1;
        }
        
        return s;
}


/*
 * You need to check this if setup_svc() returns NULL
 */
#if 0
        if (is_data_direct(&sap->blli[0])) {
                /* Try to remove possible entry in kernel */
                delete_addr(conn->atm_address);
        }
#endif
/* Does an active open to dst_addr using pre-filled
 * parameters in sap and qos.
 * If listen_addr is non NULL, binds to it.
 * Data direct SVCs are non-blocking, others block
 * Returns NULL for error or new connections.
 */
Conn_t *setup_svc(struct sockaddr_atmsvc *dst_addr,
                  struct sockaddr_atmsvc *listen_addr,
                  struct atm_sap *sap, struct atm_qos *qos)
{
        Conn_t *conn;
        int s, ret;
        char buff[MAX_ATM_ADDR_LEN+1];

        diag(COMPONENT, DIAG_DEBUG, "Outgoing call setup\n");
        
        /* The code below is commented out due to the following scenario:
           We have made a connection to another LEC and our address was
           numerically lower than theirs (LANEv2 8.1.13).
           The other end has also connected us, but we must not use that
           VCC.
           However, if the connection we made gets closed, we can not open
           it again since a connection to the destination LEC already exists.
        */
#if 0
        /* We don't create connection to an entity where we already have
           a connection. */
        if (conn_already_exists(dst_addr->sas_addr.prv, NULL) &&
            is_data_direct(&sap->blli[0]))
                return NULL;
#endif

        dst_addr->sas_family = AF_ATMSVC;
        listen_addr->sas_family = AF_ATMSVC;
        
        switch(sap->blli[0].l3.tr9577.snap[4]) { /* Kludge.  Eh? */
        case CONTROL_CONN:
                diag(COMPONENT, DIAG_DEBUG, "LE Control SVC setup\n");
                break;
        case DATA_DIRECT_8023:
                diag(COMPONENT, DIAG_DEBUG, "Data direct 802.3\n");
                break;
        case DATA_DIRECT_8025:
                diag(COMPONENT, DIAG_DEBUG, "Data direct 802.5\n");
                break;
        case MCAST_CONN_8023:
                diag(COMPONENT, DIAG_DEBUG, "Multicast 802.3\n");
                break;
        case MCAST_CONN_8025:
                diag(COMPONENT, DIAG_DEBUG, "Multicast 802.5\n");
                break;
        default:
                diag(COMPONENT, DIAG_ERROR, "Unknown codepoint in svc setup\n");
        }
 
        s = get_socket(listen_addr, sap, qos);
        if (s < 0) return NULL;
        if (atm2text(buff, sizeof(buff), (struct sockaddr *)dst_addr, A2T_PRETTY | A2T_NAME | A2T_LOCAL) < 0)
                sprintf(buff, "<Unknown ATM address>");
        diag(COMPONENT, DIAG_DEBUG, "Call to %s", buff);

        /* Make data direct SVCs non-blocking */
        if (is_data_direct(&sap->blli[0])) {
                ret = fcntl(s, F_GETFL);
                if (ret < 0) {
                        diag(COMPONENT, DIAG_ERROR, "fcntl(s, F_GETFL)\n");
                        close(s);
                } else if (fcntl(s, F_SETFL, ret|O_NONBLOCK) < 0) {
                        diag(COMPONENT, DIAG_ERROR, "fcntl(s, F_SETFL, x|O_NONBLOCK)\n");
                        close(s);
                        return NULL;
                }
        }

        ret = connect(s, (struct sockaddr *)dst_addr, sizeof(struct sockaddr_atmsvc));
        if (ret < 0 && errno != EINPROGRESS) {
                diag(COMPONENT, DIAG_ERROR, "connect error: %s\n", strerror(errno));
                close(s);
                return NULL;
        }

        conn = list_add_conn(dst_addr->sas_addr.prv);
        diag(COMPONENT, DIAG_DEBUG, "Conn:%p\n", conn);
        if (conn == NULL) {
                close(s);
                return NULL;
        }
        conn->fd = s;
        conn->type = WEMADE;
        conn->codepoint = sap->blli[0].l3.tr9577.snap[4];

        if (is_data_direct(&sap->blli[0]))
                conn->status = CONNECTING;
        else
                conn->status = CONNECTED;
        
        return conn;
}

/* Creates listen socket for incoming data direct connections.
 * Only for data direct, not for Control or Multicast listen sockets.
 * Returns < 0 for error
 */
int create_data_listen(void)
{
        struct atm_sap sap;
        struct atm_qos qos;
        struct sockaddr_atmsvc addr;

        memset(&addr, 0, sizeof(struct sockaddr_atmsvc));
        memcpy(addr.sas_addr.prv, lec_params.c1n_my_atm_addr, ATM_ESA_LEN);
        addr.sas_family = AF_ATMSVC;
        init_conn_params(&sap, &qos, DATA_DIRECT_CONN);

        lec_params.data_listen = create_listensocket(&addr, &sap, &qos);
        if (lec_params.data_listen == NULL) {
                diag(COMPONENT, DIAG_FATAL, "Could not create listen socket for incoming Data Direct VCCs\n");
                return -1;
        }

        return 0;
}

/* Opens a Multicast or non-blocking Data Direct VCC to atm_addr.
 * Not for Control connections.
 * Returns < 0 for error
 */
int create_data_svc(unsigned char *atm_addr, int codepoint)
{
        struct atm_sap sap;
        struct atm_qos qos;
        struct sockaddr_atmsvc my_addr, dst_addr;
        Conn_t *conn;

        memset(&my_addr, 0, sizeof(struct sockaddr_atmsvc));
        memcpy(my_addr.sas_addr.prv, lec_params.c1n_my_atm_addr, ATM_ESA_LEN);
        memset(&dst_addr, 0, sizeof(struct sockaddr_atmsvc));
        memcpy(dst_addr.sas_addr.prv, atm_addr, ATM_ESA_LEN);
        my_addr.sas_family = dst_addr.sas_family = AF_ATMSVC;
        init_conn_params(&sap, &qos, codepoint);

        conn = setup_svc(&dst_addr, &my_addr, &sap, &qos);
        if (conn == NULL) {
                diag(COMPONENT, DIAG_ERROR, "Could not create Data Direct VCC\n");
                delete_addr(dst_addr.sas_addr.prv);
                return -1;
        }

        return 0;
}

/* Creates a listen socket with parameters specified with
 * arguments.
 * Returns NULL for error or Conn_t for new listen socket.
 */
Conn_t *create_listensocket(struct sockaddr_atmsvc *listen_addr,
                            struct atm_sap *sap, struct atm_qos *qos)
{
        int fd, ret;
        Conn_t *conn;

        diag(COMPONENT, DIAG_DEBUG, "conn_create_listensocket\n");
        
        fd = get_socket(listen_addr, sap, qos);
        if (fd < 0) return NULL;
        
        ret = listen(fd, 5);
        if (ret != 0) {
                diag(COMPONENT, DIAG_DEBUG, "Listen failed: %s\n", strerror(errno));
                close(fd);
                return NULL;
        }
        
        conn = list_add_conn(NULL);
        if (conn == NULL) {
                diag(COMPONENT, DIAG_ERROR, "List_add_conn failed\n");
                close(fd);    
                return NULL;
        }

        conn->type = LISTENING;
        conn->fd = fd;
        diag(COMPONENT, DIAG_DEBUG, "Listen socket created blli:%2.2x %2.2x fd: %d\n",
             sap->blli[0].l3.tr9577.snap[3],
             sap->blli[0].l3.tr9577.snap[4],
             conn->fd);
        
        return conn;
}

/* Accepts a new connection from listen socket in conn.
 * Returns NULL for error
 */
Conn_t *accept_conn(Conn_t *conn)
{
        Conn_t *new;
        struct sockaddr_atmsvc addr;
        size_t len;
        int fd;
        char buff[MAX_ATM_ADDR_LEN+1];

        diag(COMPONENT, DIAG_DEBUG, "Accepting connection on fd %d\n", conn->fd);
        len = sizeof(addr);
        fd = accept(conn->fd, (struct sockaddr *)&addr, &len);
        diag(COMPONENT, DIAG_DEBUG, "accept returned %d\n", fd);
        if (fd < 0) {
                diag(COMPONENT, DIAG_ERROR, "accept: %s\n", strerror(errno));
                return NULL;
        }
        if (atm2text(buff, sizeof(buff), (struct sockaddr *)&addr, A2T_PRETTY | A2T_NAME | A2T_LOCAL) < 0)
                sprintf(buff, "<Unknown ATM address>");
        diag(COMPONENT, DIAG_DEBUG, "Call from %s", buff);

        new = list_add_conn(addr.sas_addr.prv);
        if (new == NULL) return NULL;
        new->fd = fd;
        new->status = CONNECTED;
        new->type = THEYMADE;
        if (conn == lec_params.ctrl_listen) new->codepoint  = CONTROL_CONN;
        if (conn == lec_params.mcast_listen) new->codepoint = MCAST_CONN;
        if (conn == lec_params.data_listen) new->codepoint  = DATA_DIRECT_CONN;

        return new;
}

/* Close all connections, important or not.
 */
void close_connections(void)
{
        Conn_t *conn, *next;

        for(conn = connlist; conn; conn = next) {
                diag(COMPONENT, DIAG_DEBUG, "Destroying:%p fd:%d type:%d\n",
                     conn, conn->fd, conn->type);
                next = conn->next;
                close(conn->fd);
                list_remove_conn(conn);
                free(conn);
        }

        return;
}

/* Closes a connection and checks its importance.
 * Important connections are kernel socket, LES connections,
 * BUS Default Multicast Send VCC, last Multicast Forward VCC from Bus
 * and any of the listen sockets.
 * Returns < 0 for important connection.
 */
int close_connection(Conn_t *conn)
{
        int bad = 0;
        Conn_t *mcast;

        diag(COMPONENT, DIAG_DEBUG, "close_connection %p\n", conn);

        if (conn == lec_params.kernel ||
            conn == lec_params.ctrl_direct  ||
            conn == lec_params.ctrl_dist    ||
            conn == lec_params.mcast_send   ||
            conn == lec_params.mcast_listen ||
            conn == lec_params.data_listen)
                bad = -1;
        else {
                bad = -1;
                for (mcast = connlist; mcast; mcast = mcast->next)
                        if (mcast != conn &&
                            mcast->type == THEYMADE &&
                            mcast->codepoint == MCAST_CONN)
                                bad = 0;
        }

        close(conn->fd);
        list_remove_conn(conn);
        free(conn);
        
        return bad;
}

/* Accepts a new incoming Data Direct or Multicast Forward connection.
 * Control connections (LECS/LES) are accepted during configuration/join.
 * Returns < 0 for serious error such as broken listen socket.
 */
static int handle_accept(Conn_t *conn)
{
        Conn_t *new;
        struct atmlec_ioc ioc;
        
        new = accept_conn(conn);
        if (new == NULL) return -1;

        if (conn == lec_params.mcast_listen) {
                diag(COMPONENT, DIAG_DEBUG, "Multicast Forward VCC accepted\n");
                ioc.receive = 2;
        } else {
                diag(COMPONENT, DIAG_DEBUG, "Data Direct VCC accepted\n");
                ioc.receive = 0;
                if (conn_already_exists(new->atm_address, new) &&
                    memcmp(lec_params.c1n_my_atm_addr, new->atm_address,  ATM_ESA_LEN) < 0) {
                        diag(COMPONENT, DIAG_DEBUG, "Using it only to receive, spec 8.1.1\n");
                        ioc.receive = 1;
                }
        }
        memcpy(ioc.atm_addr, new->atm_address, ATM_ESA_LEN);
        ioc.dev_num = lec_params.itf_num;
        diag(COMPONENT, DIAG_DEBUG, "Attaching a new VCC, fd %d\n", new->fd);
        if (ioctl(new->fd, ATMLEC_DATA, &ioc) < 0) {
                diag(COMPONENT, DIAG_ERROR, "VCC attach failed: ioctl: %s\n", strerror(errno));
                return -1;
        }

        return 0;
}

/* Reads a LE control frame from conn, usually Data Direct or
 * Multicast Forward connection. Calls the incoming packet
 * handler function.
 * Returns < 0 for serious error such as broken LES connection
 */
static int handle_data(Conn_t *conn)
{
        char buff[MAX_CTRL_FRAME];
        int retval;

        retval = recv_frame(conn, buff, sizeof(buff));
        if (retval < 0) {
                diag(COMPONENT, DIAG_ERROR, "handle_data: read: %s\n", strerror(errno));
                return (close_connection(conn));
        }
        if (retval == 0) {
                diag(COMPONENT, DIAG_DEBUG, "fd %d, Data or Multicast VCC closed\n", conn->fd);
                return (close_connection(conn));
        }

        return handle_frame(conn, buff, retval);
}

/* Checks connections in *fds. The only allowed sockets
 * in *fds are listen sockets, data direct and control
 * sockets.
 * Returns < 0 for serious error such as broken LES connection
 */
int check_connections(fd_set *fds)
{
        Conn_t *conn, *next;

        conn = connlist;
        while (conn != NULL) {
                next = conn->next;
                if (!FD_ISSET(conn->fd, fds)) {
                        conn = next;
                        continue;
                }

                switch (conn->type) {
                case LISTENING:
                        if (handle_accept(conn) < 0)
                                return -1;
                        break;
                case WEMADE:
                case THEYMADE:
                        if (handle_data(conn) < 0)
                                return -1;
                        break;
                default:
                        diag(COMPONENT, DIAG_ERROR, "check_connections: bad_type '%s'\n",
                             get_type_string(conn->type));
                        break;
                }
                
                conn = next;
        }
        
        return 0;
}

/* Completes a non-blocking connect.
 * Returns < 0 for serious error
 */
static int handle_connect(Conn_t *conn)
{
        int retval;
        struct sockaddr_atmsvc dummy;
        struct atmlec_msg msg;
        struct atmlec_ioc ioc;

        diag(COMPONENT, DIAG_DEBUG, "handle_connect: completing fd %d\n", conn->fd);
        /* this seems to be common method in Linux-ATM
         * making sure that nonblocking connect was
         * completed successfully
         */
        conn->status = CONNECTED;
        retval = connect(conn->fd, (struct sockaddr *)&dummy, sizeof(struct sockaddr_atmsvc));
        if (retval < 0) {
                diag(COMPONENT, DIAG_DEBUG, "handle_connect: connect: %s\n", strerror(errno));
                delete_addr(conn->atm_address);
                close_connection(conn);
                return 0;
        }

        send_ready_ind(conn);

        memcpy(ioc.atm_addr, conn->atm_address, ATM_ESA_LEN);
        ioc.dev_num = lec_params.itf_num;
        ioc.receive = 0;
        diag(COMPONENT, DIAG_DEBUG, "Attaching a new active VCC, fd %d\n", conn->fd);
        if (ioctl(conn->fd, ATMLEC_DATA, &ioc) < 0) {
                diag(COMPONENT, DIAG_ERROR, "VCC attach failed: ioctl: %s\n", strerror(errno));
                return -1;
        }

        memset(&msg, 0, sizeof(struct atmlec_msg));
        msg.type = l_flush_tran_id;
        memcpy(msg.content.normal.atm_addr, conn->atm_address, ATM_ESA_LEN);
        msg.content.normal.flag = send_flush_req(conn);
        
        msg_to_kernel(&msg, sizeof(struct atmlec_msg));


        return 0;
}

/* Complete non-blocking connections in *fds.
 * Returns < 0 for serious error (problems with kernel). 
 */
int complete_connections(fd_set *fds)
{
        Conn_t *conn, *next;
        int retval;

        conn = connlist;
        while (conn) {
                next = conn->next;
                if (FD_ISSET(conn->fd, fds)) {
                        retval = handle_connect(conn);
                        if (retval < 0) return -1;
                }
                conn = next;
        }
                
        return 0;
}

/* Send a LE control frame using *conn.
 * Returns < 0 for serious error
 */
int send_frame(Conn_t *conn, void *frame, int length)
{
        struct frame_hdr *hdr;
        int ret;

        diag(COMPONENT, DIAG_DEBUG, "send_frame: fd:%d len:%ld\n", conn->fd, length);
        hdr = (struct frame_hdr *)frame;
        if (hdr->opcode == htons(READY_QUERY) ||
            hdr->opcode == htons(READY_IND))
                diag(COMPONENT, DIAG_DEBUG, "%s\n", opcode2text(hdr->opcode));
        else 
                display_frame(frame);

        ret = write(conn->fd, frame, length);
        if (ret < 0) {
                diag(COMPONENT, DIAG_ERROR, "send_frame: write: %s\n", strerror(errno));
                return -1;
        } 

        return ret;
}

/* Receive a LE control frame from *conn.
 * Returns < 0 for serious error.
 */
int recv_frame(Conn_t *conn, void *buff, int length)
{
        int ret;

        diag(COMPONENT, DIAG_DEBUG, "recv_frame: fd:%d\n", conn->fd);
        ret = read(conn->fd, buff, length);
        if (ret < 0) {
                diag(COMPONENT, DIAG_ERROR, "Read failed: %s\n", strerror(errno));
                return -1;
        } 

#if 0
        diag(COMPONENT, DIAG_DEBUG, "recv_frame: read %d bytes\n", ret);
        if (get_verbosity(COMPONENT) >= DIAG_DEBUG) {
                int i;
                for (i = 0; i < 11; i++)
                        diag(COMPONENT, DIAG_DEBUG, "0x%2x", ((unsigned char *)buff)[i]);
        }
#endif

        return ret;
}


/*
 * LANE2: 5.2.1.4 and others, sleep random time before trying to reconnect
 */
void random_delay(void)
{
        struct timeval tv;
        int millis, interval;
        
        srand(time(NULL));
        interval = lec_params.c38_max_reconfig_delay - lec_params.c37_min_reconfig_delay;
        millis = (rand() % interval) + lec_params.c37_min_reconfig_delay;
        tv.tv_sec  = (millis - (millis % 1000)) / 1000;
        tv.tv_usec = (millis % 1000) * 1000;
        
        diag(COMPONENT, DIAG_DEBUG, "random_delay: sleeping %d.%d seconds\n", tv.tv_sec, tv.tv_usec);
        (void)select(0, NULL, NULL, NULL, &tv);
        
        return;
}

/* Collect already connected sockets in *fds
 */
void conn_get_fds(fd_set *fds)
{
        Conn_t *conn;

        diag(COMPONENT, DIAG_DEBUG, "collecting ready fds ");
        conn = connlist;
        while (conn) {
                if (conn->status != CONNECTING) {
                        FD_SET(conn->fd, fds);
                        diag(COMPONENT, DIAG_DEBUG, "%d type %s", conn->fd, get_type_string(conn->type));
                }
                conn = conn->next;
        }
        
        return;
}

/* Collect non-blocking connecting sockets in *fds
 */
void conn_get_connecting_fds(fd_set *fds)
{
        Conn_t *conn;

        diag(COMPONENT, DIAG_DEBUG, "collecting connecting fds ");
        conn = connlist;
        while (conn) {
                if (conn->status == CONNECTING) {
                        FD_SET(conn->fd, fds);
                        diag(COMPONENT, DIAG_DEBUG, "%d", conn->fd);
                }
                conn = conn->next;
        }
        
}

/* Creates Conn_t for fd and marks it as kernel socket
 * Returns < 0 for error
 */
int conn_set_kernel_socket(int fd)
{
        Conn_t *conn;

        conn = list_add_conn(NULL);
        if (conn == NULL) {
                diag(COMPONENT, DIAG_ERROR, "conn_set_kernel_socket: list_add_conn failed\n");
                return -1;
        }
        conn->type = KERNEL_SOCK;
        conn->status = CONNECTED;
        conn->fd = fd;
        lec_params.kernel = conn;

        return fd;
}

/* Creates new Conn_t object and allocates memory for it.
 * atm_addr should be the ATM address of the other end
 * or NULL if not applicable
 */
static Conn_t *list_add_conn(unsigned char *atm_addr)
{
        Conn_t *conn;

        conn = (Conn_t *)malloc(sizeof(Conn_t));
        if (!conn)
                return NULL;

        memset(conn, 0, sizeof(Conn_t));
        if (atm_addr)
                memcpy(conn->atm_address, atm_addr, ATM_ESA_LEN);

        conn->next = connlist;
        conn->previous = NULL;
        if (connlist)
                connlist->previous = conn;
        connlist = conn;
        diag(COMPONENT, DIAG_DEBUG, "Added conn:%p\n", conn);

        return conn;
}

/* Helper for close_connection and close_connections
 */
static void list_remove_conn(Conn_t *conn)
{

        if (conn->next == NULL && conn->previous == NULL
            && connlist != conn) return;
        diag(COMPONENT, DIAG_DEBUG, "Removing conn:%p fd:%d previous:%p next:%p ",
             conn, conn->fd, conn->previous, conn->next);

        if (conn->previous) 
                diag(COMPONENT, DIAG_DEBUG, "Previous:%p, fd:%d, next:%p, previous:%p ",
                     conn->previous, conn->previous->fd,
                     conn->previous->next, conn->previous->previous);
        if (conn->next)
                diag(COMPONENT, DIAG_DEBUG, "Next:%p, fd:%d next:%p, previous:%p ",
                     conn->next, conn->next->fd,
                     conn->next->next, conn->next->previous);  
        if (conn->previous) {
                conn->previous->next = conn->next;
        } else /* First in line */
                connlist = conn->next;
        if (conn->next)
                conn->next->previous = conn->previous;  
        diag(COMPONENT, DIAG_DEBUG, "Connlist: %p\n", connlist);
        conn->next=conn->previous= NULL;
        
        return;
}

static const char *get_type_string(int type)
{
        switch(type) {
        case WEMADE:
                return "WEMADE";
                break;
        case THEYMADE:
                return "THEYMADE";
                break;
        case LISTENING:
                return "LISTENING";
                break;
        case KERNEL_SOCK:
                return "KERNEL_SOCK";
                break;
        default:
                break;
        }
        
        return "UNKNOWN";
}

static int maxmtu2maxsdu(uint8_t mtu)
{

    int sdu;

    switch (mtu) {
    case MTU_1516:
        sdu = 1516;
        break;
    case MTU_1580:  /* LANE2: MTU can be 1580 too (IEEE 802.1p/Q) */
        sdu = 1580;
        break;
    case MTU_4544:
        sdu = 4544;
        break;
    case MTU_9234:
        sdu = 9234;
        break;
    case MTU_18190:
        sdu = 18190;
        break;
    default:
        sdu = 1516;
        break;
    }

    return sdu;
}

int maxmtu2itfmtu(uint8_t mtu)
{

    int sdu;

    switch (mtu) {
    case MTU_1516:
        sdu = 1500;
        break;
    case MTU_1580:  /* LANE2: MTU can be 1580 too (IEEE 802.1p/Q) */
        sdu = 1500;
        break;
    case MTU_4544:
        sdu = 4528;
        break;
    case MTU_9234:
        sdu = 9218;
        break;
    case MTU_18190:
        sdu = 18174;
        break;
    default:
        sdu = 1500;
        break;
    }

    return sdu;
}

/* Convert a type of connection (CONTROL_CONN, DATA_DIRECT and MCAST_CONN)
 * to a BLLI codepoint which depends on C2 LAN Type.
 */
static uint16_t conn_type2codepoint(int conn_type)
{
        if (conn_type == CONTROL_CONN) return CONTROL_CONN;

        if (conn_type == DATA_DIRECT_CONN) {
                if (lec_params.c2_lan_type == LAN_TYPE_8023 || lec_params.c2_lan_type == LAN_TYPE_UNSPEC)
                        return DATA_DIRECT_8023;
                else if (lec_params.c2_lan_type == LAN_TYPE_8025)
                        return DATA_DIRECT_8025;
                diag(COMPONENT, DIAG_ERROR, "conn_type2codepoint, bad lan_type %d", lec_params.c2_lan_type);
                return DATA_DIRECT_8023;
        }
        else if (conn_type == MCAST_CONN) {
                if (lec_params.c2_lan_type == LAN_TYPE_8023 || lec_params.c2_lan_type == LAN_TYPE_UNSPEC)
                        return MCAST_CONN_8023;
                else if (lec_params.c2_lan_type == LAN_TYPE_8025)
                        return MCAST_CONN_8025;
                diag(COMPONENT, DIAG_ERROR, "conn_type2codepoint, bad lan_type %d", lec_params.c2_lan_type);
                return MCAST_CONN_8023;
        }

        diag(COMPONENT, DIAG_ERROR, "conn_type2codepoint, unknown type %d", conn_type);

        return DATA_DIRECT_8023;
}
