#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>

#include "sctpAssocTable.h"
#include "sctpAssocLocalAddrTable.h"
#include "sctpAssocRemAddrTable.h"
#include "sctpTables_common.h"

#include "mibgroup/util_funcs/get_pid_from_inode.h"

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

/*
 * Linux provides sctp statistics in /proc/net/sctp/assoc and 
 * /proc/net/sctp/remaddr. The 'assoc' file covers sctpAssocTable and 
 * sctpAssocLocalAddrTable, the later one contains sctpAssocRemAddrTable.
 * 
 * Linux does *not* provide *StartTime timestamps. This implementation tries
 * to guess these timestamps.  
 */


#define PROC_PREFIX          "/proc"
#define ASSOC_FILE           PROC_PREFIX "/net/sctp/assocs"
#define REMADDR_FILE         PROC_PREFIX "/net/sctp/remaddr"

/*
 * Convert string with ipv4 or ipv6 address to provided buffer.
 */
static int
convert_address(char *token, char *addr_buffer, u_long * addr_type,
                u_long * addr_len)
{
    int             family;
    int             ret;

    if (strchr(token, ':') != NULL) {
        family = AF_INET6;
        *addr_type = INETADDRESSTYPE_IPV6;
        *addr_len = 16;
    } else {
        family = AF_INET;
        *addr_type = INETADDRESSTYPE_IPV4;
        *addr_len = 4;
    }
    ret = inet_pton(family, token, addr_buffer);

    if (ret <= 0)
        return SNMP_ERR_GENERR;
    return SNMP_ERR_NOERROR;
}


/*
 * Parse local address part from assoc file. It assumes that strtok will return
 * these addresses. The addresses are separated by space and the list ends
 * with "<->". 
 */
static int
parse_assoc_local_addresses(sctpAssocTable_entry * entry,
                            sctpTables_containers * containers)
{
    char           *token;
    int             ret;
    /*
     * parse all local addresses 
     */

    while ((token = strtok(NULL, " "))) {
        sctpAssocLocalAddrTable_entry *localAddr;
        char           *ip = token;

        if (token[0] == '<')
            break;              /* local addresses finished */

        if (token[0] == '*')
            ip = token + 1;

        localAddr = sctpAssocLocalAddrTable_entry_create();
        if (localAddr == NULL)
            return SNMP_ERR_GENERR;

        localAddr->sctpAssocId = entry->sctpAssocId;
        ret =
            convert_address(ip, localAddr->sctpAssocLocalAddr,
                            &localAddr->sctpAssocLocalAddrType,
                            &localAddr->sctpAssocLocalAddr_len);
        if (ret != SNMP_ERR_NOERROR) {
            sctpAssocLocalAddrTable_entry_free(localAddr);
            return SNMP_ERR_GENERR;
        }

        ret =
            sctpAssocLocalAddrTable_add_or_update(containers->
                                                  sctpAssocLocalAddrTable,
                                                  localAddr);
        if (ret != SNMP_ERR_NOERROR)
            return SNMP_ERR_GENERR;
    }
    return SNMP_ERR_NOERROR;
}

/*
 * Parse primary remote address part from assoc file. It assumes that strtok will return
 * all remote addresses. The addresses are separated by space and the list ends with \t .
 */
static int
parse_assoc_remote_addresses(sctpAssocTable_entry * entry)
{
    char           *token;
    int             ret = SNMP_ERR_GENERR;

    while ((token = strtok(NULL, " ")) && (token[0] != '\t')) {
        if (token[0] == '*') {
            /*
             * that's the primary address 
             */
            ret =
                convert_address(token + 1, entry->sctpAssocRemPrimAddr,
                                &entry->sctpAssocRemPrimAddrType,
                                &entry->sctpAssocRemPrimAddr_len);
        }
    }
    return ret;
}

static int
parse_assoc_line(char *line, sctpTables_containers * containers)
{
    unsigned long long inode;
    char           *token;
    int             ret;
    sctpAssocTable_entry *entry;

    entry = sctpAssocTable_entry_create();
    if (entry == NULL)
        return SNMP_ERR_GENERR;

    token = strtok(line, " ");  /* ASSOC, ignore */
    token = strtok(NULL, " ");  /* SOCK, ignore */
    token = strtok(NULL, " ");  /* STY, ignore */
    token = strtok(NULL, " ");  /* SST, ignore */
    token = strtok(NULL, " ");  /* ST */
    if (token == NULL) {
        ret = SNMP_ERR_GENERR;
        goto error;
    }
    entry->sctpAssocState = strtol(token, NULL, 10);

    token = strtok(NULL, " ");  /* HBKT, ignore */
    token = strtok(NULL, " ");  /* ASSOC-ID, store */
    if (token == NULL) {
        ret = SNMP_ERR_GENERR;
        goto error;
    }
    entry->sctpAssocId = strtol(token, NULL, 10);

    token = strtok(NULL, " ");  /* TX_QUEUE, ignore */
    token = strtok(NULL, " ");  /* RX_QUEUE, ignore */
    token = strtok(NULL, " ");  /* UID, ignore */

    token = strtok(NULL, " ");  /* INODE */
    if (token == NULL) {
        ret = SNMP_ERR_GENERR;
        goto error;
    }
    inode = strtoull(token, NULL, 10);
    entry->sctpAssocPrimProcess = netsnmp_get_pid_from_inode(inode);

    token = strtok(NULL, " ");  /* LPORT */
    if (token == NULL) {
        ret = SNMP_ERR_GENERR;
        goto error;
    }
    entry->sctpAssocLocalPort = strtol(token, NULL, 10);

    token = strtok(NULL, " ");  /* RPORT */
    if (token == NULL) {
        ret = SNMP_ERR_GENERR;
        goto error;
    }
    entry->sctpAssocRemPort = strtol(token, NULL, 10);

    ret = parse_assoc_local_addresses(entry, containers);
    if (ret != SNMP_ERR_NOERROR)
        goto error;

    ret = parse_assoc_remote_addresses(entry);
    if (ret != SNMP_ERR_NOERROR)
        goto error;

    token = strtok(NULL, " ");  /* HBINT */
    if (token == NULL) {
        ret = SNMP_ERR_GENERR;
        goto error;
    }
    entry->sctpAssocHeartBeatInterval = strtol(token, NULL, 10);

    token = strtok(NULL, " ");  /* INS */
    if (token == NULL) {
        ret = SNMP_ERR_GENERR;
        goto error;
    }
    entry->sctpAssocInStreams = strtol(token, NULL, 10);

    token = strtok(NULL, " ");  /* OUTS */
    if (token == NULL) {
        ret = SNMP_ERR_GENERR;
        goto error;
    }
    entry->sctpAssocOutStreams = strtol(token, NULL, 10);

    token = strtok(NULL, " ");  /* MAXRT */
    if (token == NULL) {
        ret = SNMP_ERR_GENERR;
        goto error;
    }
    entry->sctpAssocMaxRetr = strtol(token, NULL, 10);

    token = strtok(NULL, " ");  /* T1X */
    if (token == NULL) {
        ret = SNMP_ERR_GENERR;
        goto error;
    }
    entry->sctpAssocT1expireds = strtol(token, NULL, 10);

    token = strtok(NULL, " ");  /* T2X */
    if (token == NULL) {
        ret = SNMP_ERR_GENERR;
        goto error;
    }
    entry->sctpAssocT2expireds = strtol(token, NULL, 10);

    token = strtok(NULL, " ");  /* RXTC */
    if (token == NULL) {
        ret = SNMP_ERR_GENERR;
        goto error;
    }
    entry->sctpAssocRtxChunks = strtol(token, NULL, 10);

    entry->sctpAssocRemHostName[0] = 0;
    entry->sctpAssocRemHostName_len = 0;
    entry->sctpAssocDiscontinuityTime = 0;

    ret = sctpAssocTable_add_or_update(containers->sctpAssocTable, entry);
    if (ret != SNMP_ERR_NOERROR) {
        DEBUGMSGTL(("sctp:tables:load:assoc",
                    "error adding/updating the entry in container\n"));
        return ret;
    }

    return SNMP_ERR_NOERROR;

  error:
    if (entry != NULL)
        sctpAssocTable_entry_free(entry);
    return ret;
}


/*
 * Load assocTable and localAddrTable from /proc/net/sctp/assoc. Mark all added
 * or updated entries as valid (so the missing, i.e. invalid, can be deleted).
 */
static int
load_assoc(sctpTables_containers * containers)
{
    FILE           *f;
    char            line[1024];
    int             ret = SNMP_ERR_NOERROR;

    DEBUGMSGTL(("sctp:tables:load:assoc", "arch load(linux)\n"));

    f = fopen(ASSOC_FILE, "r");
    if (f == NULL) {
        DEBUGMSGTL(("sctp:tables:load:assoc",
                    "arch load failed: can't open" ASSOC_FILE "\n"));
        return SNMP_ERR_GENERR;
    }

    netsnmp_get_pid_from_inode_init();

    /*
     * ignore the header. 
     */
    fgets(line, sizeof(line), f);

    while (fgets(line, sizeof(line), f) != NULL) {
        DEBUGMSGTL(("sctp:tables:load:assoc", "processing line: %s\n",
                    line));

        ret = parse_assoc_line(line, containers);
        if (ret != SNMP_ERR_NOERROR) {
            DEBUGMSGTL(("sctp:tables:load:assoc",
                        "error parsing the line\n"));
        }
    }
    fclose(f);

    return SNMP_ERR_NOERROR;
}


static int
parse_remaddr_line(char *line, sctpTables_containers * containers)
{
    char           *token;
    int             ret;
    sctpAssocRemAddrTable_entry *entry;

    entry = sctpAssocRemAddrTable_entry_create();
    if (entry == NULL)
        return SNMP_ERR_GENERR;

    token = strtok(line, " ");  /* rem. address */
    ret =
        convert_address(token, entry->sctpAssocRemAddr,
                        &entry->sctpAssocRemAddrType,
                        &entry->sctpAssocRemAddr_len);
    if (ret < 0) {
        ret = SNMP_ERR_GENERR;
        goto error;
    }

    token = strtok(NULL, " ");  /* assoc id */
    if (token == NULL) {
        ret = SNMP_ERR_GENERR;
        goto error;
    }
    entry->sctpAssocId = strtol(token, NULL, 10);

    token = strtok(NULL, " ");  /* hb act */
    if (token == NULL) {
        ret = SNMP_ERR_GENERR;
        goto error;
    }
    if (token[0] == '1')
        entry->sctpAssocRemAddrHBActive = TRUTHVALUE_TRUE;
    else
        entry->sctpAssocRemAddrHBActive = TRUTHVALUE_FALSE;

    token = strtok(NULL, " ");  /* rto */
    if (token == NULL) {
        ret = SNMP_ERR_GENERR;
        goto error;
    }
    entry->sctpAssocRemAddrRTO = strtol(token, NULL, 10);

    token = strtok(NULL, " ");  /* max path rtx */
    if (token == NULL) {
        ret = SNMP_ERR_GENERR;
        goto error;
    }
    entry->sctpAssocRemAddrMaxPathRtx = strtol(token, NULL, 10);

    token = strtok(NULL, " ");  /* rem addr rtx */
    if (token == NULL) {
        ret = SNMP_ERR_GENERR;
        goto error;
    }
    entry->sctpAssocRemAddrRtx = strtol(token, NULL, 10);

    entry->sctpAssocRemAddrStartTime = 0;
    entry->sctpAssocRemAddrActive = TRUTHVALUE_TRUE;

    ret =
        sctpAssocRemAddrTable_add_or_update(containers->
                                            sctpAssocRemAddrTable, entry);
    if (ret != SNMP_ERR_NOERROR) {
        DEBUGMSGTL(("sctp:load:remaddr",
                    "error adding/updating the entry in container\n"));
        return ret;
    }

    return SNMP_ERR_NOERROR;

  error:
    if (entry != NULL)
        sctpAssocRemAddrTable_entry_free(entry);
    return ret;
}

/*
 * Load sctpAssocRemAddrTable from /proc/net/sctp/remaddr. Mark all added
 * or updated entries as valid (so the missing, i.e. invalid, can be deleted).
 */
static int
load_remaddr(sctpTables_containers * containers)
{
    FILE           *f;
    char            line[1024];
    int             ret = SNMP_ERR_NOERROR;

    DEBUGMSGTL(("sctp:load:remaddr", "arch load(linux)\n"));

    f = fopen(REMADDR_FILE, "r");
    if (f == NULL) {
        DEBUGMSGTL(("sctp:load:remaddr",
                    "arch load failed: can't open" REMADDR_FILE "\n"));
        return SNMP_ERR_GENERR;
    }

    /*
     * ignore the header. 
     */
    fgets(line, sizeof(line), f);

    while (fgets(line, sizeof(line), f) != NULL) {
        DEBUGMSGTL(("sctp:load:remaddr", "processing line: %s\n", line));

        ret = parse_remaddr_line(line, containers);
        if (ret != SNMP_ERR_NOERROR) {
            DEBUGMSGTL(("sctp:load:remaddr", "error parsing the line\n"));
        }
    }
    fclose(f);

    return SNMP_ERR_NOERROR;
}


int
sctpTables_arch_load(sctpTables_containers * containers, u_long * flags)
{
    int             ret = SNMP_ERR_NOERROR;

    *flags |= SCTP_TABLES_LOAD_FLAG_DELETE_INVALID;
    *flags |= SCTP_TABLES_LOAD_FLAG_AUTO_LOOKUP;

    ret = load_assoc(containers);
    if (ret != SNMP_ERR_NOERROR)
        return ret;

    ret = load_remaddr(containers);
    if (ret != SNMP_ERR_NOERROR)
        return ret;

    return ret;
}
