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

#include "sctpTables_common.h"
#include "sctpAssocTable.h"
#include "sctpAssocRemAddrTable.h"
#include "sctpAssocLocalAddrTable.h"
#include "sctpLookupLocalPortTable.h"
#include "sctpLookupRemPortTable.h"
#include "sctpLookupRemHostNameTable.h"
#include "sctpLookupRemPrimIPAddrTable.h"
#include "sctpLookupRemIPAddrTable.h"

netsnmp_feature_require(container_lifo);

static void
sctpAssocTable_collect_invalid(void *what, void *magic)
{
    sctpAssocTable_entry *entry = what;
    netsnmp_container *to_delete = magic;

    if (entry->valid)
        entry->valid = 0;
    else
        CONTAINER_INSERT(to_delete, entry);
}

/*
 * Remove all entries from sctpAssocTable, which are not marked as valid.
 * All valid entries are then marked as invalid (to delete them in next cache
 * load, if the entry is not updated). 
 */
void
sctpAssocTable_delete_invalid(netsnmp_container *assocTable)
{
    netsnmp_container *to_delete = netsnmp_container_find("lifo");

    CONTAINER_FOR_EACH(assocTable, sctpAssocTable_collect_invalid,
                       to_delete);

    while (CONTAINER_SIZE(to_delete)) {
        sctpAssocTable_entry *entry = CONTAINER_FIRST(to_delete);
        CONTAINER_REMOVE(assocTable, entry);
        sctpAssocTable_entry_free(entry);
        CONTAINER_REMOVE(to_delete, NULL);
    }
    CONTAINER_FREE(to_delete);
}

static void
sctpAssocRemAddrTable_collect_invalid(void *what, void *magic)
{
    sctpAssocRemAddrTable_entry *entry = what;
    netsnmp_container *to_delete = magic;

    if (entry->valid)
        entry->valid = 0;
    else
        CONTAINER_INSERT(to_delete, entry);
}

/*
 * Remove all entries from sctpAssocRemAddrTable, which are not marked as valid.
 * All valid entries are then marked as invalid (to delete them in next cache
 * load, if the entry is not updated). 
 */
void
sctpAssocRemAddrTable_delete_invalid(netsnmp_container *remAddrTable)
{
    netsnmp_container *to_delete = netsnmp_container_find("lifo");

    CONTAINER_FOR_EACH(remAddrTable, sctpAssocRemAddrTable_collect_invalid,
                       to_delete);

    while (CONTAINER_SIZE(to_delete)) {
        sctpAssocRemAddrTable_entry *entry = CONTAINER_FIRST(to_delete);
        CONTAINER_REMOVE(remAddrTable, entry);
        sctpAssocRemAddrTable_entry_free(entry);
        CONTAINER_REMOVE(to_delete, NULL);
    }
    CONTAINER_FREE(to_delete);
}

static void
sctpAssocLocalAddrTable_collect_invalid(void *what, void *magic)
{
    sctpAssocLocalAddrTable_entry *entry = what;
    netsnmp_container *to_delete = magic;

    if (entry->valid)
        entry->valid = 0;
    else
        CONTAINER_INSERT(to_delete, entry);
}

/*
 * Remove all entries from sctpAssocLocalAddrTable, which are not marked as valid.
 * All valid entries are then marked as invalid (to delete them in next cache
 * load, if the entry is not updated). 
 */
void
sctpAssocLocalAddrTable_delete_invalid(netsnmp_container *localAddrTable)
{
    netsnmp_container *to_delete = netsnmp_container_find("lifo");

    CONTAINER_FOR_EACH(localAddrTable,
                       sctpAssocLocalAddrTable_collect_invalid, to_delete);

    while (CONTAINER_SIZE(to_delete)) {
        sctpAssocLocalAddrTable_entry *entry = CONTAINER_FIRST(to_delete);
        CONTAINER_REMOVE(localAddrTable, entry);
        sctpAssocLocalAddrTable_entry_free(entry);
        CONTAINER_REMOVE(to_delete, NULL);
    }
    CONTAINER_FREE(to_delete);
}


int
sctpAssocRemAddrTable_add_or_update(netsnmp_container *remAddrTable,
                                    sctpAssocRemAddrTable_entry * entry)
{
    /*
     * we have full sctpAssocLocalAddrTable entry, update or add it in the container 
     */
    sctpAssocRemAddrTable_entry *old;

    entry->valid = 1;

    /*
     * try to find it in the container 
     */
    sctpAssocRemAddrTable_entry_update_index(entry);
    old = CONTAINER_FIND(remAddrTable, entry);

    if (old != NULL) {
        /*
         * update existing entry, don't overwrite the timestamp 
         */
        time_t          timestamp = old->sctpAssocRemAddrStartTime;
        if (timestamp == 0 && entry->sctpAssocRemAddrStartTime == 0)
            timestamp = netsnmp_get_agent_uptime();     /* set the timestamp if it was not set before */
        sctpAssocRemAddrTable_entry_copy(entry, old);
        old->sctpAssocRemAddrStartTime = timestamp;
        sctpAssocRemAddrTable_entry_free(entry);

    } else {
        /*
         * the entry is new, add it there 
         */
        if (entry->sctpAssocRemAddrStartTime == 0) {
            entry->sctpAssocRemAddrStartTime = netsnmp_get_agent_uptime();
        }
        CONTAINER_INSERT(remAddrTable, entry);
    }

    return SNMP_ERR_NOERROR;
}

int
sctpAssocLocalAddrTable_add_or_update(netsnmp_container *localAddrTable,
                                      sctpAssocLocalAddrTable_entry *
                                      entry)
{
    /*
     * we have full sctpAssocLocalAddrTable entry, update or add it in the container 
     */
    sctpAssocLocalAddrTable_entry *old;

    entry->valid = 1;

    /*
     * try to find it in the container 
     */
    sctpAssocLocalAddrTable_entry_update_index(entry);
    old = CONTAINER_FIND(localAddrTable, entry);

    if (old != NULL) {
        /*
         * update existing entry, don't overwrite the timestamp
         */
        time_t          timestamp = old->sctpAssocLocalAddrStartTime;
        if (timestamp == 0 && entry->sctpAssocLocalAddrStartTime == 0)
            timestamp = netsnmp_get_agent_uptime();     /* set the timestamp if it was not set before */
        sctpAssocLocalAddrTable_entry_copy(entry, old);
        old->sctpAssocLocalAddrStartTime = timestamp;
        sctpAssocLocalAddrTable_entry_free(entry);

    } else {
        /*
         * the entry is new, add it there 
         */
        if (entry->sctpAssocLocalAddrStartTime == 0) {
            entry->sctpAssocLocalAddrStartTime =
                netsnmp_get_agent_uptime();
        }
        CONTAINER_INSERT(localAddrTable, entry);
    }

    return SNMP_ERR_NOERROR;
}

int
sctpAssocTable_add_or_update(netsnmp_container *assocTable,
                             sctpAssocTable_entry * entry)
{
    /*
     * we have full sctpAssocTable entry, update or add it in the container 
     */
    sctpAssocTable_entry *old;

    entry->valid = 1;

    /*
     * try to find it in the container 
     */
    sctpAssocTable_entry_update_index(entry);
    old = CONTAINER_FIND(assocTable, entry);

    if (old != NULL) {
        /*
         * update existing entry, don't overwrite the timestamp
         */
        time_t          timestamp = old->sctpAssocStartTime;
        if (timestamp == 0 && entry->sctpAssocStartTime == 0
            && entry->sctpAssocState >= SCTPASSOCSTATE_ESTABLISHED)
            timestamp = netsnmp_get_agent_uptime();     /* set the timestamp if it was not set before and entry reaches the right state */
        sctpAssocTable_entry_copy(entry, old);
        old->sctpAssocStartTime = timestamp;
        sctpAssocTable_entry_free(entry);

    } else {
        /*
         * the entry is new, add it there 
         */
        if (entry->sctpAssocStartTime == 0
            && entry->sctpAssocState >= SCTPASSOCSTATE_ESTABLISHED) {
            entry->sctpAssocStartTime = netsnmp_get_agent_uptime();
        }
        CONTAINER_INSERT(assocTable, entry);
    }

    return SNMP_ERR_NOERROR;
}

static void
sctpTables_add_local_port(sctpAssocTable_entry * assoc,
                          sctpTables_containers * containers)
{
    sctpLookupLocalPortTable_entry *entry;

    entry = sctpLookupLocalPortTable_entry_create();
    if (entry == NULL) {
        DEBUGMSGTL(("sctp:tables:fill_lookup",
                    "cannot create sctpLookupLocalPortTable_entry"));
        return;
    }

    entry->sctpAssocId = assoc->sctpAssocId;
    entry->sctpAssocLocalPort = assoc->sctpAssocLocalPort;
    entry->sctpLookupLocalPortStartTime = assoc->sctpAssocStartTime;
    sctpLookupLocalPortTable_entry_update_index(entry);
    CONTAINER_INSERT(containers->sctpLookupLocalPortTable, entry);
}

static void
sctpTables_add_rem_port(sctpAssocTable_entry * assoc,
                        sctpTables_containers * containers)
{
    sctpLookupRemPortTable_entry *entry;

    entry = sctpLookupRemPortTable_entry_create();
    if (entry == NULL) {
        DEBUGMSGTL(("sctp:tables:fill_lookup",
                    "cannot create sctpLookupRemPortTable_entry"));
        return;
    }

    entry->sctpAssocId = assoc->sctpAssocId;
    entry->sctpAssocRemPort = assoc->sctpAssocRemPort;
    entry->sctpLookupRemPortStartTime = assoc->sctpAssocStartTime;
    sctpLookupRemPortTable_entry_update_index(entry);
    CONTAINER_INSERT(containers->sctpLookupRemPortTable, entry);
}

static void
sctpTables_add_rem_hostname(sctpAssocTable_entry * assoc,
                            sctpTables_containers * containers)
{
    sctpLookupRemHostNameTable_entry *entry;

    if (assoc->sctpAssocRemHostName_len == 0)
        return;                 /* the association does not know its hostname */

    entry = sctpLookupRemHostNameTable_entry_create();
    if (entry == NULL) {
        DEBUGMSGTL(("sctp:tables:fill_lookup",
                    "cannot create sctpLookupRemHostNameTable_entry"));
        return;
    }

    entry->sctpAssocId = assoc->sctpAssocId;
    entry->sctpAssocRemHostName_len = assoc->sctpAssocRemHostName_len;
    memcpy(entry->sctpAssocRemHostName, assoc->sctpAssocRemHostName,
           assoc->sctpAssocRemHostName_len);
    entry->sctpLookupRemHostNameStartTime = assoc->sctpAssocStartTime;

    sctpLookupRemHostNameTable_entry_update_index(entry);
    CONTAINER_INSERT(containers->sctpLookupRemHostNameTable, entry);
}

static void
sctpTables_add_rem_prim_ip_addr(sctpAssocTable_entry * assoc,
                                sctpTables_containers * containers)
{
    sctpLookupRemPrimIPAddrTable_entry *entry;

    entry = sctpLookupRemPrimIPAddrTable_entry_create();
    if (entry == NULL) {
        DEBUGMSGTL(("sctp:tables:fill_lookup",
                    "cannot create sctpLookupRemPrimIPAddrTable_entry"));
        return;
    }

    entry->sctpAssocId = assoc->sctpAssocId;
    entry->sctpAssocRemPrimAddrType = assoc->sctpAssocRemPrimAddrType;
    entry->sctpAssocRemPrimAddr_len = assoc->sctpAssocRemPrimAddr_len;
    memcpy(entry->sctpAssocRemPrimAddr, assoc->sctpAssocRemPrimAddr,
           assoc->sctpAssocRemPrimAddr_len);
    entry->sctpLookupRemPrimIPAddrStartTime = assoc->sctpAssocStartTime;

    sctpLookupRemPrimIPAddrTable_entry_update_index(entry);
    CONTAINER_INSERT(containers->sctpLookupRemPrimIPAddrTable, entry);
}

static void
sctpTables_fill_lookup_assoc(void *what, void *magic)
{
    sctpAssocTable_entry *entry = what;
    sctpTables_containers *containers = magic;

    sctpTables_add_local_port(entry, containers);
    sctpTables_add_rem_port(entry, containers);
    sctpTables_add_rem_hostname(entry, containers);
    sctpTables_add_rem_prim_ip_addr(entry, containers);
}

static void
sctpTables_add_rem_ip_addr(sctpAssocRemAddrTable_entry * rem_addr,
                           sctpTables_containers * containers)
{
    sctpLookupRemIPAddrTable_entry *entry;

    entry = sctpLookupRemIPAddrTable_entry_create();
    if (entry == NULL) {
        DEBUGMSGTL(("sctp:tables:fill_lookup",
                    "cannot create sctpLookupRemIPAddrTable_entry"));
        return;
    }

    entry->sctpAssocId = rem_addr->sctpAssocId;
    entry->sctpAssocRemAddrType = rem_addr->sctpAssocRemAddrType;
    entry->sctpAssocRemAddr_len = rem_addr->sctpAssocRemAddr_len;
    memcpy(entry->sctpAssocRemAddr, rem_addr->sctpAssocRemAddr,
           rem_addr->sctpAssocRemAddr_len);
    entry->sctpLookupRemIPAddrStartTime =
        rem_addr->sctpAssocRemAddrStartTime;

    sctpLookupRemIPAddrTable_entry_update_index(entry);
    CONTAINER_INSERT(containers->sctpLookupRemIPAddrTable, entry);
}

static void
sctpTables_fill_lookup_rem_addr(void *what, void *magic)
{
    sctpAssocRemAddrTable_entry *entry = what;
    sctpTables_containers *containers = magic;
    sctpTables_add_rem_ip_addr(entry, containers);
}

int
sctpTables_fill_lookup(sctpTables_containers * containers)
{
    /*
     * clear all the lookup tables 
     */
    sctpLookupLocalPortTable_container_clear(containers->
                                             sctpLookupLocalPortTable);
    sctpLookupRemPortTable_container_clear(containers->
                                           sctpLookupRemPortTable);
    sctpLookupRemHostNameTable_container_clear(containers->
                                               sctpLookupRemHostNameTable);
    sctpLookupRemPrimIPAddrTable_container_clear(containers->
                                                 sctpLookupRemPrimIPAddrTable);
    sctpLookupRemIPAddrTable_container_clear(containers->
                                             sctpLookupRemIPAddrTable);

    /*
     * fill the lookup tables 
     */
    CONTAINER_FOR_EACH(containers->sctpAssocTable,
                       sctpTables_fill_lookup_assoc, containers);
    CONTAINER_FOR_EACH(containers->sctpAssocRemAddrTable,
                       sctpTables_fill_lookup_rem_addr, containers);

    return SNMP_ERR_NOERROR;
}



int
sctpTables_load(void)
{
    sctpTables_containers containers;
    int             ret;
    u_long          flags = 0;

    containers.sctpAssocTable = sctpAssocTable_get_container();
    containers.sctpAssocRemAddrTable =
        sctpAssocRemAddrTable_get_container();
    containers.sctpAssocLocalAddrTable =
        sctpAssocLocalAddrTable_get_container();
    containers.sctpLookupLocalPortTable =
        sctpLookupLocalPortTable_get_container();
    containers.sctpLookupRemPortTable =
        sctpLookupRemPortTable_get_container();
    containers.sctpLookupRemHostNameTable =
        sctpLookupRemHostNameTable_get_container();
    containers.sctpLookupRemPrimIPAddrTable =
        sctpLookupRemPrimIPAddrTable_get_container();
    containers.sctpLookupRemIPAddrTable =
        sctpLookupRemIPAddrTable_get_container();

    ret = sctpTables_arch_load(&containers, &flags);

    if (flags & SCTP_TABLES_LOAD_FLAG_DELETE_INVALID) {
        sctpAssocTable_delete_invalid(containers.sctpAssocTable);
        sctpAssocRemAddrTable_delete_invalid(containers.
                                             sctpAssocRemAddrTable);
        sctpAssocLocalAddrTable_delete_invalid(containers.
                                               sctpAssocLocalAddrTable);
    }

    if (flags & SCTP_TABLES_LOAD_FLAG_AUTO_LOOKUP) {
        ret = sctpTables_fill_lookup(&containers);
    }

    return ret;
}
