/* 
 * Copyright (c) 2016 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 <vnet/vnet.h>
#include <stdint.h>
#include <time.h>
#include <string.h>
#include <vppinfra/mem.h>
#include "math64.h"
#include "pot_util.h"

pot_main_t pot_main;

static void pot_profile_cleanup(pot_profile *profile);

static void pot_main_profiles_reset (void)
{
    pot_main_t *sm = &pot_main;
    int i = 0;

    for (i = 0; i < MAX_POT_PROFILES; i++)
    {
      pot_profile_cleanup(&(sm->profile_list[i]));
    }
    sm->active_profile_id = 0;
    if (sm->profile_list_name)
	vec_free(sm->profile_list_name);
    sm->profile_list_name = NULL;
}

int pot_util_init (void)
{
    pot_main_profiles_reset();
    
    return(0);
}

static void pot_profile_init(pot_profile * new, u8 id)
{
    if (new)
    {
        memset(new, 0, sizeof(pot_profile));
        new->id = id;
    }
}

pot_profile *pot_profile_find(u8 id)
{
    pot_main_t *sm = &pot_main;

    if (id < MAX_POT_PROFILES)
    {
        return (&(sm->profile_list[id]));
    }
    return (NULL);
}
static int pot_profile_name_equal (u8 *name0, u8 *name1)
{
    int len0, len1;

    len0 = vec_len (name0);
    len1 = vec_len (name1);
    if (len0 != len1)
        return(0);
    return (0==strncmp ((char *) name0, (char *)name1, len0));
}

int pot_profile_list_is_enabled (u8 *name)
{
    pot_main_t *sm = &pot_main;
    return (pot_profile_name_equal(sm->profile_list_name, name));
}

void pot_profile_list_init(u8 * profile_list_name)
{
    pot_main_t *sm = &pot_main;
    int i = 0;

    /* If it is the same profile list skip reset */
    if (pot_profile_name_equal(sm->profile_list_name, profile_list_name))
    {
      return;
    }

    pot_main_profiles_reset();
    if (vec_len(profile_list_name))
      sm->profile_list_name = (u8 *)vec_dup(profile_list_name);
    else
      sm->profile_list_name = 0;
    sm->active_profile_id = 0;
    
    for (i = 0; i < MAX_POT_PROFILES; i++)
    {
      pot_profile_init(&(sm->profile_list[i]), i);
    }
}

static void pot_profile_cleanup(pot_profile * profile)
{
    u16 id = profile->id;

    memset(profile, 0, sizeof(pot_profile));
    profile->id = id;           /* Restore id alone */
}

int pot_profile_create(pot_profile * profile, u64 prime,
    u64 poly2, u64 lpc, u64 secret_share)
{
    if (profile && !profile->in_use)
    {
        pot_profile_cleanup(profile);
        profile->prime = prime;
        profile->primeinv = 1.0 / prime;
        profile->lpc = lpc;
        profile->poly_pre_eval = poly2;
        profile->secret_share = secret_share;
	profile->total_pkts_using_this_profile = 0;
        profile->valid = 1;
	return(0);
    }
    
    return(-1);
}

int pot_set_validator(pot_profile * profile, u64 key)
{
    if (profile && !profile->in_use)
    {
        profile->validator = 1;
        profile->secret_key = key;
	return(0);
    }
    return(-1);
}

always_inline u64 pot_update_cumulative_inline(u64 cumulative, u64 random,
    u64 secret_share, u64 prime, u64 lpc, u64 pre_split, double prime_inv)
{
    u64 share_random = 0;
    u64 cumulative_new = 0;

    /* 
     * calculate split share for random
     */
    share_random = add64_mod(pre_split, random, prime, prime_inv);

    /* 
     * lpc * (share_secret + share_random)
     */
    share_random = add64_mod(share_random, secret_share, prime, prime_inv);
    share_random = mul64_mod(share_random, lpc, prime, prime_inv);

    cumulative_new = add64_mod(cumulative, share_random, prime, prime_inv);

    return (cumulative_new);
}

u64 pot_update_cumulative(pot_profile * profile, u64 cumulative, u64 random)
{
    if (profile && profile->valid != 0)
    {
        return (pot_update_cumulative_inline(cumulative, random, profile->secret_share,
                profile->prime, profile->lpc, profile->poly_pre_eval,
                profile->primeinv));
    }
    return (0);
}

always_inline u8 pot_validate_inline(u64 secret, u64 prime, double prime_inv,
    u64 cumulative, u64 random)
{
    if (cumulative == (random + secret))
    {
        return (1);
    }
    else if (cumulative == add64_mod(random, secret, prime, prime_inv))
    {
        return (1);
    }
    return (0);
}

/* 
 * return True if the cumulative matches secret from a profile
 */
u8 pot_validate(pot_profile * profile, u64 cumulative, u64 random)
{
    if (profile && profile->validator)
    {
        return (pot_validate_inline(profile->secret_key, profile->prime,
                profile->primeinv, cumulative, random));
    }
    return (0);
}

/* 
 * Utility function to get random number per pack
 */
u64 pot_generate_random(pot_profile * profile)
{
    u64 random = 0;
    int32_t second_half;
    static u32 seed = 0;

    if (PREDICT_FALSE(!seed))
        seed = random_default_seed();

    /* 
     * Upper 4 bytes seconds
     */
    random = (u64) time(NULL);

    random &= 0xffffffff;
    random = random << 32;
    /* 
     * Lower 4 bytes random number
     */
    second_half = random_u32(&seed);

    random |= second_half;

    if (PREDICT_TRUE(profile != NULL))
    {
        random &= profile->bit_mask;
    }
    return (random);
}

int pot_profile_set_bit_mask(pot_profile * profile, u16 bits)
{
    int sizeInBits;

    if (profile && !profile->in_use)
    {
        sizeInBits = sizeof(profile->bit_mask) * 8;
        profile->bit_mask =
            (bits >=
            sizeInBits ? (u64) - 1 : (u64) ((u64) 1 << (u64) bits) - 1);
	return(0);
    }
    return(-1);
}

clib_error_t *clear_pot_profile_command_fn(vlib_main_t * vm,
    unformat_input_t * input, vlib_cli_command_t * cmd)
{

    pot_main_profiles_reset();
    
    return 0;
}

void clear_pot_profiles()
{
    clear_pot_profile_command_fn(0, 0, 0);
}

VLIB_CLI_COMMAND(clear_pot_profile_command) =
{
.path = "clear pot profile",
.short_help = "clear pot profile [<index>|all]",
.function = clear_pot_profile_command_fn,
};

static clib_error_t *set_pot_profile_command_fn(vlib_main_t * vm,
    unformat_input_t * input, vlib_cli_command_t * cmd)
{
    u64 prime;
    u64 secret_share;
    u64 secret_key;
    u8 validator = 0;
    u32 profile_id = ~0;
    u32 bits;
    u64 lpc = 0, poly2 = 0;
    pot_profile *profile = NULL;
    u8 *profile_list_name = NULL;
    
    bits = MAX_BITS;

    while (unformat_check_input(input) != UNFORMAT_END_OF_INPUT)
    {
        if (unformat(input, "name %s",
		     &profile_list_name));
        else if (unformat(input, "id %d", &profile_id))
            ;
        else if (unformat(input, "validate-key 0x%Lx", &secret_key))
            validator = 1;
        else if (unformat(input, "prime-number 0x%Lx", &prime))
            ;
        else if (unformat(input, "secret_share 0x%Lx", &secret_share))
            ;
        else if (unformat(input, "polynomial2 0x%Lx", &poly2))
            ;
        else if (unformat(input, "lpc 0x%Lx", &lpc))
            ;
        else if (unformat(input, "bits-in-random %d", &bits))
        {
            if (bits > MAX_BITS)
                bits = MAX_BITS;
        }
        else
            return clib_error_return(0, "unknown input `%U'",
                format_unformat_error, input);
    }
    if (profile_list_name == 0)
    {
        return clib_error_return(0, "Name cannot be null");
    }	
    pot_profile_list_init(profile_list_name);
    profile = pot_profile_find(profile_id);

    if (profile)
    {
        pot_profile_create(profile, prime, poly2, lpc, secret_share);
        if (validator)
            pot_set_validator(profile, secret_key);
        pot_profile_set_bit_mask(profile, bits);
    }
    vec_free(profile_list_name);
    return 0;
}

VLIB_CLI_COMMAND(set_pot_profile_command) =
{
.path = "set pot profile",
.short_help = "set pot profile name <string> id [0-1] [validator-key 0xu64] \
                  prime-number 0xu64 secret_share 0xu64 lpc 0xu64 \
                  polynomial2 0xu64 bits-in-random [0-64] ",
.function = set_pot_profile_command_fn,
};

static clib_error_t *set_pot_profile_activate_command_fn(vlib_main_t * vm,
    unformat_input_t * input, vlib_cli_command_t * cmd)
{
    pot_main_t *sm = &pot_main;
    u8 *profile_list_name = NULL;
    u32 id = 0;
    clib_error_t *result = NULL;
    
    while (unformat_check_input(input) != UNFORMAT_END_OF_INPUT)
    {
        if (unformat(input, "name %s",
		     &profile_list_name));
        else if (unformat(input, "id %d", &id))
            ;
        else
            return clib_error_return(0, "unknown input `%U'",
                format_unformat_error, input);
    }
    if (profile_list_name == 0)
    {
        return clib_error_return(0, "Name cannot be null");
    }

    if (!pot_profile_list_is_enabled(profile_list_name)) {
        result = clib_error_return(0, "%s list is not enabled, profile in use %s",
				 profile_list_name, sm->profile_list_name);
    } else if (0 != pot_profile_set_active((u8)id)) {
        result = clib_error_return(0, "Profile %d not defined in %s",
				 id, sm->profile_list_name);
    }
    vec_free(profile_list_name);
    return result;
}

VLIB_CLI_COMMAND(set_pot_profile_activate_command) =
{
.path = "set pot profile-active",
.short_help = "set pot profile-active name <string> id [0-1]",
.function = set_pot_profile_activate_command_fn,
};

static clib_error_t *show_pot_profile_command_fn(vlib_main_t * vm,
    unformat_input_t * input, vlib_cli_command_t * cmd)
{
    pot_main_t *sm = &pot_main;
    pot_profile *p = NULL;
    u16 i;
    u8 *s = 0;

    if (vec_len(sm->profile_list_name) == 0)
    {
        s = format(s, "POT Profiles not configured\n");
        vlib_cli_output(vm, "%v", s);
        return 0;
    }
    s = format(s, "Profile list in use  : %s\n",sm->profile_list_name);
    for (i = 0; i < MAX_POT_PROFILES; i++)
    {
        p = pot_profile_find(i);
        if (p->valid == 0)
            continue;
        s = format(s, "POT Profile at index: %d\n", i);
        s = format(s, "                 Id : %d\n", p->id);
        s = format(s, "          Validator : %s (%d)\n",
            (p->validator) ? "True" : "False", p->validator);
        if (p->validator == 1)
            s = format(s, "         Secret key : 0x%Lx (%Ld)\n",
                p->secret_key, p->secret_key);
        s = format(s, "       Secret share : 0x%Lx (%Ld)\n",
            p->secret_share, p->secret_share);
        s = format(s, "       Prime number : 0x%Lx (%Ld)\n",
            p->prime, p->prime);
        s = format(s, "2nd polynomial(eval) : 0x%Lx (%Ld)\n",
            p->poly_pre_eval, p->poly_pre_eval);
        s = format(s, "                 LPC : 0x%Lx (%Ld)\n", p->lpc, p->lpc);

        s = format(s, "           Bit mask : 0x%Lx (%Ld)\n",
            p->bit_mask, p->bit_mask);
    }

    p = pot_profile_find(sm->active_profile_id);

    if (p && p->valid && p->in_use) {
        s = format(s, "\nProfile index in use: %d\n", sm->active_profile_id);
        s = format(s, "Pkts passed : 0x%Lx (%Ld)\n",
		   p->total_pkts_using_this_profile,
		   p->total_pkts_using_this_profile);
        if (pot_is_decap(p))
            s = format(s, "  This is Decap node.  \n");
    } else {
        s = format(s, "\nProfile index in use: None\n");
    }
    vlib_cli_output(vm, "%v", s);
    vec_free(s);

    return 0;
}

VLIB_CLI_COMMAND(show_pot_profile_command) =
{
.path = "show pot profile",
.short_help = "show pot profile",
.function = show_pot_profile_command_fn,
};
