/*
 *---------------------------------------------------------------------------
 * cnat_db_scanner.c - cnat_db_scanner dispatch function and initialization
 *
 * Copyright (c) 2009-2014 Cisco and/or its affiliates.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *---------------------------------------------------------------------------
 */

#include <vlib/vlib.h>
#include <vnet/vnet.h>
#include <vppinfra/error.h>
#include <vnet/buffer.h>
#include <vppinfra/string.h>
#include <vppinfra/random.h>
#include <vppinfra/fifo.h>
#include <vppinfra/hash.h>
#include <vppinfra/format.h>


#include "cnat_db.h"
#include "cnat_logging.h"
#include "cnat_global.h"
#include "cnat_ipv4_udp.h"
#include "cnat_common_api.h"

u32 translation_create_count, translation_delete_count;
u32 translation_create_rate, translation_delete_rate;

u32 in2out_forwarding_count, out2in_forwarding_count;
u32 in2out_forwarding_rate,  out2in_forwarding_rate;

u32 nat44_active_translations;
u32 num_entries;
uword check_these_pool_indices[2*MAX_DB_ENTRY_SELECTED_PER_SCAN];

#define   CNAT_DB_SCANNER_TURN_ON   5  /* just an arbitary number for easier debugging */

//extern u32 pcp_throttle_count;

typedef struct {
  u32 cached_next_index;
  /* $$$$ add data here */

  /* convenience variables */
  vlib_main_t * vlib_main;
  vnet_main_t * vnet_main;
} cnat_db_scanner_main_t;

cnat_db_scanner_main_t cnat_db_scanner_main;


static inline void check_session_for_expiry(
    	cnat_session_entry_t * sdb, u8 timeout_dirty
	/*,dslite_table_entry_t *dslite_entry_ptr*/)
{
    void cnat_delete_session_db_entry (cnat_session_entry_t *ep, u8 log);
    /* Tasks -
     * 1. Check for expiry for this entry
     * 2. Delete if expired
     */
    u32 timeout = 0;

    switch(sdb->v4_dest_key.k.vrf & CNAT_PRO_MASK) {
        case CNAT_TCP:
            if (sdb->flags & CNAT_DB_FLAG_TCP_ACTIVE) {    
                timeout = sdb->timeout;
                if(PREDICT_FALSE(timeout_dirty)) {
                    timeout = query_and_update_db_timeout(
                        (void *)sdb, SESSION_DB_TYPE);
                }
                if(PREDICT_TRUE(timeout == 0)) {
                    timeout = tcp_active_timeout;
                    //dslite_entry_ptr->timeout_info.tcp_active_timeout;
                }
            } else {
                timeout = tcp_initial_setup_timeout;
                //dslite_entry_ptr->timeout_info.tcp_initial_setup_timeout;
            }
            break;
        case CNAT_UDP:
            if (sdb->flags & CNAT_DB_FLAG_UDP_ACTIVE) {
                timeout = sdb->timeout;
                if(PREDICT_FALSE(timeout_dirty)) {
                    timeout = query_and_update_db_timeout(
                        (void *)sdb, SESSION_DB_TYPE);
                }

                if(PREDICT_TRUE(timeout == 0)) {
                    timeout = udp_act_session_timeout;
                    //dslite_entry_ptr->timeout_info.udp_act_session_timeout;
                }
            } else {
                timeout = udp_init_session_timeout;
                //dslite_entry_ptr->timeout_info.udp_init_session_timeout;
            }
            break;
        case CNAT_ICMP:
            timeout = icmp_session_timeout;
                //dslite_entry_ptr->timeout_info.icmp_session_timeout;
            break;
        case CNAT_PPTP:
            timeout = pptp_cfg.timeout;
            break;
        default:
            return;
    }
    /* Changes required for clearing sessions */
    if (PREDICT_FALSE((sdb->entry_expires == 0) ||
                        (sdb->entry_expires + timeout < cnat_current_time))) {
        cnat_delete_session_db_entry(sdb, TRUE);
    }
}

static u8 handle_db_scan_for_sessions(
    	cnat_main_db_entry_t *db, int *dirty_index, uword db_index
    	/* ,dslite_table_entry_t *dslite_entry_ptr */)
{
    /* Tasks -
     * 1. Traverse through the sessions and check for timeouts
     * 2. Delete sessions that have exipred
     * 3. Check if the db has only one session remaining.. if so,
     *    the details of the session has to be moved to main db
     *    and session db entry needs to be freed
     * 4. If db does not have any sessions left, the db itself
     *    needs to be deleted.
     */
    u32 nsessions, session_index_head, session_index;
    cnat_session_entry_t *sdb;
    u8 timeout_dirty = FALSE;

    if(PREDICT_FALSE(*dirty_index == db_index)) {
        *dirty_index = -1;
    }
    if(PREDICT_FALSE(timeout_dirty_flag == 1)) {
        timeout_dirty_flag = 0;
        *dirty_index = db_index;
        timeout_dirty = TRUE;
    }

    session_index_head = session_index = db->session_head_index;
    nsessions = db->nsessions;

    do {
        sdb = cnat_session_db + session_index;
        if(PREDICT_FALSE(!sdb)) {
            //TO DO: Debug msg?
            return FALSE;
        }
        session_index = sdb->main_list.next;
        check_session_for_expiry(sdb, timeout_dirty /*,dslite_entry_ptr*/);
        nsessions--; /* To ensure that we do not get in to an infinite loop */
      } while(session_index != session_index_head
          && db->session_head_index != EMPTY &&
          nsessions);

    /* Note.. the code below assumes that while deleting the
     * sessions, we do not delete the main db entry if it does
     * not have any sessions anymore
     */
    if(PREDICT_FALSE((!db->nsessions) &&
        (!(db->flags & CNAT_DB_FLAG_STATIC_PORT)))) {
         cnat_delete_main_db_entry_v2(db);
         return TRUE; /* to indicate that main db was deleted */
    }
    return FALSE;
}

static void cnat_db_scanner(void)
{
    cnat_main_db_entry_t * db;
    u32 timeout;
    cnat_vrfmap_t *my_vrfmap __attribute__((unused)) = 0;
    static int dirty_index = -1;
    u16 instance __attribute__((unused));
    //dslite_table_entry_t *dslite_entry_ptr;
    u32 i;
    uword db_index;
    //pcp_throttle_count = 0;

    for (i = 0; i < num_entries; i++) {
        db_index = check_these_pool_indices[i];
        db = cnat_main_db + db_index;
        timeout=0;
        my_vrfmap = 0;

#if 0
        if(PREDICT_FALSE(db->flags & CNAT_PCP_FLAG)) {
    
            if(db->proto_data.seq_pcp.pcp_lifetime < cnat_current_time) {
                /* mark as implicit */
                db->flags &= ~CNAT_PCP_FLAG;
            }
            continue;
        }

#endif
        if(PREDICT_FALSE(db->nsessions > 1)) {
            if(PREDICT_FALSE(
                handle_db_scan_for_sessions(db, &dirty_index, db_index /*,dslite_entry_ptr */)))            {
                continue;
            } else if(PREDICT_TRUE(db->nsessions > 1)) {
                continue;
            }
            /* if there is exactly one dest left.. let it fall through
            * and check if that needs to be deleted as well
            */
        }

#if 0 
        if (PREDICT_FALSE(db->flags & CNAT_DB_FLAG_STATIC_PORT)) {
            if (PREDICT_FALSE(db->flags & CNAT_DB_DSLITE_FLAG)) {
	        if(PREDICT_FALSE(
                    ((dslite_entry_ptr->nf_logging_policy != SESSION_LOG_ENABLE) &&
                    (dslite_entry_ptr->syslog_logging_policy != SESSION_LOG_ENABLE))
                    || (db->nsessions !=1))) {
                     continue;
	        }
            } else {
	        my_vrfmap = cnat_map_by_vrf + db->vrfmap_index;
                if(PREDICT_FALSE(
                    ((my_vrfmap->nf_logging_policy != SESSION_LOG_ENABLE) &&
                     (my_vrfmap->syslog_logging_policy != SESSION_LOG_ENABLE)) ||
                    (db->nsessions !=1))) {
                    continue;
                } 	
	    }
        }
#endif

        switch(db->in2out_key.k.vrf & CNAT_PRO_MASK) {
            case CNAT_TCP:
                if (db->flags & CNAT_DB_FLAG_TCP_ACTIVE) {
                    timeout = db->timeout;
                    if(PREDICT_FALSE(dirty_index == db_index)) {
                        dirty_index = -1;
                    }
                    if(PREDICT_FALSE(timeout_dirty_flag == 1)) {
                        timeout_dirty_flag = 0;
                        dirty_index = db_index;
                    }
                    if(PREDICT_FALSE(dirty_index != -1)) {
                        timeout = query_and_update_db_timeout(
                            (void *)db, MAIN_DB_TYPE);
                    }
                    if(PREDICT_TRUE(timeout == 0)) {
                        timeout = tcp_active_timeout;
                    }
                } else {
                    timeout = tcp_initial_setup_timeout;
                }
                break;
            case CNAT_UDP:
                if (db->flags & CNAT_DB_FLAG_UDP_ACTIVE) {
                    timeout = db->timeout;
                    if(PREDICT_FALSE(dirty_index == db_index)) {
                        dirty_index = -1;
                    }
                    if(PREDICT_FALSE(timeout_dirty_flag == 1)) {
                        timeout_dirty_flag = 0;
                        dirty_index = db_index;
                    }
                    if(PREDICT_FALSE(dirty_index != -1)) {
                        timeout = query_and_update_db_timeout(
                            (void *)db, MAIN_DB_TYPE);
                    }
                    if(PREDICT_TRUE(timeout == 0)) {
                        timeout = udp_act_session_timeout;
                    }
                } else {
                    timeout = udp_init_session_timeout;
                }
                break;
            case CNAT_ICMP:
                timeout = icmp_session_timeout;
                break;
            case CNAT_PPTP:
                timeout = pptp_cfg.timeout;
                break;
            default:
                continue;
        }


        /* Ref: CSCtu97536 */
        if (PREDICT_FALSE((db->entry_expires  == 0) || 
                    (db->entry_expires + timeout < cnat_current_time))) {
#if 0
	    if (PREDICT_FALSE(db->flags & CNAT_DB_FLAG_STATIC_PORT)) {
    	        if (PREDICT_FALSE(db->flags & CNAT_DB_DSLITE_FLAG)) {
		    instance = db->dslite_nat44_inst_id;
    	        } else {
		    instance = NAT44_RESERVED_INST_ID;
                    cnat_session_log_nat44_mapping_delete(db, 0, my_vrfmap);
	        }

                /* Reset the session details */
                db->nsessions = 0;
                db->dst_ipv4 = 0;
                db->dst_port = 0;
                db->flags &= ~(CNAT_DB_FLAG_TCP_ACTIVE | CNAT_DB_FLAG_UDP_ACTIVE
                        | CNAT_DB_FLAG_ALG_ENTRY);
                db->timeout = 0;
                db->entry_expires = 0;
                db->alg.delta = 0;
                db->proto_data.seq_pcp.tcp_seq_num = 0;
                continue;
            }
#endif
            //printf("DELETING DB ENTRY FOR 0x%x\n", db->in2out_key.k.ipv4);
            cnat_delete_main_db_entry_v2(db);
        }
        //free(check_these_pool_indices[i]);
    }
}

static void walk_the_db (void)
{
    pool_header_t *h = pool_header(cnat_main_db);
    u32 db_uword_len;
    static u32 base_index = 0, free_bitmap_index = 0;
    int bits_scanned = 0, i;
    uword inuse_bitmap;

    num_entries=0;
    
    /* Across all db entries... */
    db_uword_len = vec_len(cnat_main_db) / NUM_BITS_IN_UWORD;
    if (PREDICT_FALSE(vec_len(cnat_main_db) % NUM_BITS_IN_UWORD)) {
        /*
         * It should not come here as in cnat_db_init_v2()
         * it is made multiple of NUM_BITS_IN_UWORD
         */
        ASSERT(0);
        return ;
    }

    if (PREDICT_FALSE(! db_uword_len))
        return ;

    while (bits_scanned < MAX_DB_ENTRY_PER_SCAN) {

        if (PREDICT_FALSE(free_bitmap_index < vec_len(h->free_bitmap))) {

            /* free_bitmap exists and it is not all 0 */

            inuse_bitmap = ~(h->free_bitmap[free_bitmap_index]);
            i = 0;
            while (inuse_bitmap) {

                /* Check to see if the index is in use */
                if (PREDICT_FALSE((inuse_bitmap >> i) & 1)) {
                    check_these_pool_indices[num_entries] = base_index + i;
                    inuse_bitmap &= ~((uword) 1 << i);
                    num_entries++;
                }
                i++;
            } // while (inuse_bitmap)
        } else {

            /*
             * 64-bit entry is 0, means all 64 entries are allocated.
             * So, simply add all 64 entries here.
             * No need to form inuse_bitmap, check and reset bits
             */
            for (i=0; i<NUM_BITS_IN_UWORD; i++) {

                check_these_pool_indices[num_entries] = base_index + i;
                num_entries++;
            }
        } // if (free_bitmap_index < vec_len(h->free_bitmap))

        /* Update free_bitmap_index and base_index for next run */
        if (PREDICT_FALSE(free_bitmap_index == db_uword_len - 1)) {
            /* wrap-around for next run */
            free_bitmap_index = 0;
            base_index = 0;
        } else {
            free_bitmap_index ++;
            base_index += NUM_BITS_IN_UWORD;
        }

        /* increment # of bits scanned */
        bits_scanned += NUM_BITS_IN_UWORD;

        /* Found enough entries to check ? */
        if (PREDICT_FALSE(num_entries >= MAX_DB_ENTRY_SELECTED_PER_SCAN))
        {
            /* This check is introduced to keep fixed MAX scan entry value */
            /* This is very much required when we do scanning for NAT64 */
            /* please check comments in cnat_db_scanner() & 
             * handler_nat64_db_scanner() */
            if (num_entries >= MAX_COMBINED_DB_ENTRIES_PER_SCAN) {
                num_entries = MAX_COMBINED_DB_ENTRIES_PER_SCAN;
            }
            break;
        }

    } // while (bits_scanned < MAX_DB_ENTRY_PER_SCAN)

    if (PREDICT_FALSE(num_entries > 0)) {
	//printf("%s: num_entries [%d]\n", __func__, num_entries);
        cnat_db_scanner(); 
    }
    return ;
}

static uword cnat_db_scanner_fn (vlib_main_t * vm,
                              vlib_node_runtime_t * node,
                              vlib_frame_t * frame)
{
  f64 timeout = 0.01;  /* timeout value in sec (10 ms) */
  static u8 timeout_count = 0;

  uword event_type;
  uword * event_data = 0;
  /* Wait until vCGN is configured */
  while (1) {
      /* Assigning a huge timeout value, vCGN may or 
       * may not get configured within this timeout  */
      vlib_process_wait_for_event_or_clock (vm, 1e9);  
      event_type = vlib_process_get_events (vm, &event_data);

      /* check whether the process is waken up by correct guy, 
       * otherwise continue waiting for the vCGN config */
      if (event_type == CNAT_DB_SCANNER_TURN_ON) {
          break;
      } 
  }

  while(1) {
    vlib_process_suspend(vm, timeout);
    
    /* Above suspend API should serve the purpose, no need to invoke wait API */
    /* vlib_process_wait_for_event_or_clock (vm, timeout); */

     /* Lets make use of this timeout for netflow packet sent */
     if (timeout_count < 100) { /* 100*10 ms = 1 sec */
         timeout_count++;
     } else {
         if (nfv9_configured) {
             handle_pending_nfv9_pkts();
         }
         timeout_count = 0;
     }
     /* Do we need this ? */
     //event_type = vlib_process_get_events (vm, &event_data);
     cnat_current_time = (u32)vlib_time_now (vm);
     if (cnat_db_init_done) {
        walk_the_db();	
     }
  }

  return 0;
}


VLIB_REGISTER_NODE (cnat_db_scanner_node) = {
    .function = cnat_db_scanner_fn,
    .type = VLIB_NODE_TYPE_PROCESS,
    .name = "cnat-db-scanner",
    .process_log2_n_stack_bytes = 18,
};

clib_error_t *cnat_db_scanner_init (vlib_main_t *vm)
{
  cnat_db_scanner_main_t *mp = &cnat_db_scanner_main;

  mp->vlib_main = vm;
  mp->vnet_main = vnet_get_main();

  return 0;
}

void cnat_scanner_db_process_turn_on(vlib_main_t *vm)
{
    vlib_process_signal_event (vm, cnat_db_scanner_node.index,
                             CNAT_DB_SCANNER_TURN_ON, 0);
    return; 
}

VLIB_INIT_FUNCTION (cnat_db_scanner_init);