/*
 * 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 <vlibmemory/api.h>
#include <vnet/fib/fib_api.h>
#include <vnet/ip/ip_types_api.h>
#include <vnet/fib/fib_table.h>
#include <vnet/mfib/mfib_table.h>
#include <vnet/bier/bier_disp_table.h>
#include <vpp/api/types.h>
#include <vnet/classify/vnet_classify.h>
#include <vnet/ip/ip_format_fns.h>

#include <vnet/fib/fib.api_enum.h>
#include <vnet/fib/fib.api_types.h>

static u16 fib_base_msg_id;
#define REPLY_MSG_ID_BASE fib_base_msg_id
#include <vlibapi/api_helper_macros.h>

int
fib_api_table_id_decode (fib_protocol_t fproto,
                         u32 table_id,
                         u32 *fib_index)
{
    *fib_index = fib_table_find(fproto, table_id);

    if (INDEX_INVALID == *fib_index)
    {
        return VNET_API_ERROR_NO_SUCH_FIB;
    }

    return (0);
}

int
fib_api_mtable_id_decode (fib_protocol_t fproto,
                          u32 table_id,
                          u32 *fib_index)
{
    *fib_index = mfib_table_find(fproto, table_id);

    if (~0 == *fib_index)
    {
        return VNET_API_ERROR_NO_SUCH_FIB;
    }

    return (0);
}

static void
fib_api_next_hop_decode (const vl_api_fib_path_t *in,
                         ip46_address_t *out)
{
    ASSERT (FIB_API_PATH_NH_PROTO_IP4 == in->proto || FIB_API_PATH_NH_PROTO_IP6 == in->proto);
    *out = to_ip46 (FIB_API_PATH_NH_PROTO_IP6 == in->proto, (void *)&in->nh.address);
}

static vl_api_fib_path_nh_proto_t
fib_api_path_dpo_proto_to_nh (dpo_proto_t dproto)
{
    switch (dproto)
    {
    case DPO_PROTO_IP4:
        return (FIB_API_PATH_NH_PROTO_IP4);
    case DPO_PROTO_IP6:
        return (FIB_API_PATH_NH_PROTO_IP6);
    case DPO_PROTO_MPLS:
        return (FIB_API_PATH_NH_PROTO_MPLS);
    case DPO_PROTO_BIER:
        return (FIB_API_PATH_NH_PROTO_BIER);
    case DPO_PROTO_ETHERNET:
        return (FIB_API_PATH_NH_PROTO_ETHERNET);
    case DPO_PROTO_NSH:
        ASSERT(0);
        break;
    }
    return (FIB_API_PATH_NH_PROTO_IP4);
}


static void
fib_api_next_hop_encode (const fib_route_path_t *rpath,
                         vl_api_fib_path_t *fp)
{
    fp->proto = fib_api_path_dpo_proto_to_nh(rpath->frp_proto);

    if (rpath->frp_proto == DPO_PROTO_IP4)
        clib_memcpy (&fp->nh.address.ip4,
                &rpath->frp_addr.ip4,
                sizeof (rpath->frp_addr.ip4));
    else if (rpath->frp_proto == DPO_PROTO_IP6)
        clib_memcpy (&fp->nh.address.ip6,
                &rpath->frp_addr.ip6,
                sizeof (rpath->frp_addr.ip6));
}

static int
fib_api_path_nh_proto_to_dpo (vl_api_fib_path_nh_proto_t pp,
                              dpo_proto_t *dproto)
{
    switch (pp)
    {
    case FIB_API_PATH_NH_PROTO_IP4:
        *dproto = DPO_PROTO_IP4;
        break;
    case FIB_API_PATH_NH_PROTO_IP6:
        *dproto = DPO_PROTO_IP6;
        break;
    case FIB_API_PATH_NH_PROTO_MPLS:
        *dproto = DPO_PROTO_MPLS;
        break;
    case FIB_API_PATH_NH_PROTO_BIER:
        *dproto = DPO_PROTO_BIER;
        break;
    case FIB_API_PATH_NH_PROTO_ETHERNET:
        *dproto = DPO_PROTO_ETHERNET;
        break;
    default:
        return (-1);
    }
    return (0);
}

int
fib_api_path_decode (vl_api_fib_path_t *in,
                     fib_route_path_t *out)
{
    vnet_classify_main_t *cm = &vnet_classify_main;
    int rv = 0, n_labels;
    vnet_main_t *vnm;
    u8 ii;

    vnm = vnet_get_main ();
    clib_memset(&out->frp_dpo, 0, sizeof(out->frp_dpo));

    /* enums are u32 */
    in->flags = ntohl (in->flags);
    in->type = ntohl (in->type);
    in->proto = ntohl (in->proto);

    /*
     * attributes that apply to all path types
     */
    out->frp_flags = 0;
    out->frp_weight = in->weight;
    if (0 == out->frp_weight)
    {
        out->frp_weight = 1;
    }
    out->frp_preference = in->preference;

    rv = fib_api_path_nh_proto_to_dpo(in->proto, &out->frp_proto);

    if (0 != rv)
        return (rv);

    /*
     * convert the flags and the AFI to determine the path type
     */
    if (in->flags & FIB_API_PATH_FLAG_RESOLVE_VIA_HOST)
        out->frp_flags |= FIB_ROUTE_PATH_RESOLVE_VIA_HOST;
    if (in->flags & FIB_API_PATH_FLAG_RESOLVE_VIA_ATTACHED)
        out->frp_flags |= FIB_ROUTE_PATH_RESOLVE_VIA_ATTACHED;
    if (in->flags & FIB_API_PATH_FLAG_POP_PW_CW)
        out->frp_flags |= FIB_ROUTE_PATH_POP_PW_CW;

    switch (in->type)
    {
    case FIB_API_PATH_TYPE_DVR:
        out->frp_sw_if_index = ntohl(in->sw_if_index);
        out->frp_flags |= FIB_ROUTE_PATH_DVR;
        break;
    case FIB_API_PATH_TYPE_INTERFACE_RX:
        out->frp_sw_if_index = ntohl(in->sw_if_index);
        out->frp_flags |= FIB_ROUTE_PATH_INTF_RX;
        break;
    case FIB_API_PATH_TYPE_DROP:
        out->frp_flags |= FIB_ROUTE_PATH_DROP;
        break;
    case FIB_API_PATH_TYPE_LOCAL:
        out->frp_flags |= FIB_ROUTE_PATH_LOCAL;
        out->frp_sw_if_index = ntohl(in->sw_if_index);
        break;
    case FIB_API_PATH_TYPE_ICMP_UNREACH:
        out->frp_flags |= FIB_ROUTE_PATH_ICMP_UNREACH;
        break;
    case FIB_API_PATH_TYPE_ICMP_PROHIBIT:
        out->frp_flags |= FIB_ROUTE_PATH_ICMP_PROHIBIT;
        break;
    case FIB_API_PATH_TYPE_CLASSIFY:
        out->frp_flags |= FIB_ROUTE_PATH_CLASSIFY;

        if (pool_is_free_index (cm->tables, ntohl (in->nh.classify_table_index)))
        {
            return VNET_API_ERROR_NO_SUCH_TABLE;
        }
        out->frp_classify_table_id = ntohl (in->nh.classify_table_index);
        break;
    case FIB_API_PATH_TYPE_UDP_ENCAP:
        out->frp_flags |= FIB_ROUTE_PATH_UDP_ENCAP;
        out->frp_udp_encap_id = ntohl(in->nh.obj_id);
        break;
    case FIB_API_PATH_TYPE_BIER_IMP:
        out->frp_flags |= FIB_ROUTE_PATH_BIER_IMP;
        out->frp_bier_imp = ntohl (in->nh.obj_id);
        break;

    case FIB_API_PATH_TYPE_SOURCE_LOOKUP:
        out->frp_flags |= FIB_ROUTE_PATH_SOURCE_LOOKUP;
        /* fall through */
    case FIB_API_PATH_TYPE_NORMAL:
        switch (out->frp_proto)
        {
        case DPO_PROTO_IP4:
        case DPO_PROTO_IP6:
            fib_api_next_hop_decode(in, &out->frp_addr);
            out->frp_sw_if_index = ntohl(in->sw_if_index);
            out->frp_rpf_id = ntohl(in->rpf_id);

            if (0 == out->frp_rpf_id)
            {
                /* allow 0 to be an unset value on the API */
                out->frp_rpf_id = ~0;
            }

            if (~0 != out->frp_rpf_id)
            {
                out->frp_flags |= FIB_ROUTE_PATH_RPF_ID;
            }

            if (~0 == out->frp_sw_if_index)
            {
                /* recursive or deag, validate the next-hop FIB */
                if (~0 != out->frp_rpf_id)
                {
                    rv = fib_api_mtable_id_decode(
                        dpo_proto_to_fib(out->frp_proto),
                        ntohl(in->table_id),
                        &out->frp_fib_index);
                }
                else
                {
                    rv = fib_api_table_id_decode(
                        dpo_proto_to_fib(out->frp_proto),
                        ntohl(in->table_id),
                        &out->frp_fib_index);
                }
                if (0 != rv)
                {
                    return (rv);
                }
            }
            else
            {
                if (pool_is_free_index (vnm->interface_main.sw_interfaces,
                                        out->frp_sw_if_index))
                {
                    return VNET_API_ERROR_NO_MATCHING_INTERFACE;
                }
            }

            if (ip46_address_is_zero(&out->frp_addr))
            {
                if (~0 == out->frp_sw_if_index &&
                    ~0 != out->frp_fib_index)
                {
                    out->frp_flags |= FIB_ROUTE_PATH_DEAG;
                }
            }

            break;
        case DPO_PROTO_MPLS:
            out->frp_local_label = ntohl (in->nh.via_label);
            out->frp_eos = MPLS_NON_EOS;
            out->frp_sw_if_index = ~0;
            break;
        case DPO_PROTO_BIER:
            out->frp_sw_if_index = ntohl(in->sw_if_index);
            out->frp_rpf_id = ntohl(in->rpf_id);

            if (!(out->frp_flags & FIB_ROUTE_PATH_BIER_IMP))
            {
                index_t bdti;

                bdti = bier_disp_table_find(ntohl(in->table_id));

                if (INDEX_INVALID != bdti)
                {
                    out->frp_fib_index = bdti;
                }
                else
                {
                    return (VNET_API_ERROR_NO_SUCH_FIB);
                }
            }
            break;
        case DPO_PROTO_ETHERNET:
            out->frp_sw_if_index = ntohl(in->sw_if_index);
            break;
        case DPO_PROTO_NSH:
            break;
        }
    }

    n_labels = in->n_labels;
    if (n_labels != 0)
    {
        vec_validate (out->frp_label_stack, n_labels - 1);
        for (ii = 0; ii < n_labels; ii++)
        {
            out->frp_label_stack[ii].fml_value =
                ntohl(in->label_stack[ii].label);
            out->frp_label_stack[ii].fml_ttl =
                in->label_stack[ii].ttl;
            out->frp_label_stack[ii].fml_exp =
                in->label_stack[ii].exp;
            out->frp_label_stack[ii].fml_mode =
                (in->label_stack[ii].is_uniform ?
                 FIB_MPLS_LSP_MODE_UNIFORM :
                 FIB_MPLS_LSP_MODE_PIPE);
        }
    }

    return (0);
}

void
fib_api_path_encode (const fib_route_path_t * rpath,
                     vl_api_fib_path_t *out)
{
    memset (out, 0, sizeof (*out));

    out->weight = rpath->frp_weight;
    out->preference = rpath->frp_preference;
    out->sw_if_index = htonl (rpath->frp_sw_if_index);
    out->proto = fib_api_path_dpo_proto_to_nh(rpath->frp_proto);
    out->rpf_id = rpath->frp_rpf_id;
    fib_api_next_hop_encode (rpath, out);

    if (0 != rpath->frp_fib_index)
    {
        if ((DPO_PROTO_IP6 == rpath->frp_proto) ||
            (DPO_PROTO_IP4 == rpath->frp_proto))
        {
            if (rpath->frp_flags & FIB_ROUTE_PATH_RPF_ID)
            {
                out->table_id = htonl (mfib_table_get_table_id(
                                           rpath->frp_fib_index,
                                           dpo_proto_to_fib(rpath->frp_proto)));
            }
            else
            {
                out->table_id = htonl (fib_table_get_table_id(
                                           rpath->frp_fib_index,
                                           dpo_proto_to_fib(rpath->frp_proto)));
            }
        }
    }

    if (rpath->frp_flags & FIB_ROUTE_PATH_DVR)
    {
        out->type = FIB_API_PATH_TYPE_DVR;
    }
    else if (rpath->frp_flags & FIB_ROUTE_PATH_ICMP_UNREACH)
    {
        out->type = FIB_API_PATH_TYPE_ICMP_UNREACH;
    }
    else if (rpath->frp_flags & FIB_ROUTE_PATH_ICMP_PROHIBIT)
    {
        out->type = FIB_API_PATH_TYPE_ICMP_PROHIBIT;
    }
    else if (rpath->frp_flags & FIB_ROUTE_PATH_LOCAL)
    {
        out->type = FIB_API_PATH_TYPE_LOCAL;
    }
    else if (rpath->frp_flags & FIB_ROUTE_PATH_DROP)
    {
        out->type = FIB_API_PATH_TYPE_DROP;
    }
    else if (rpath->frp_flags & FIB_ROUTE_PATH_UDP_ENCAP)
    {
        out->type = FIB_API_PATH_TYPE_UDP_ENCAP;
        out->nh.obj_id = rpath->frp_udp_encap_id;
    }
    else if (rpath->frp_flags & FIB_ROUTE_PATH_BIER_IMP)
    {
        out->type = FIB_API_PATH_TYPE_BIER_IMP;
        out->nh.obj_id = rpath->frp_bier_imp;
    }
    else if (rpath->frp_flags & FIB_ROUTE_PATH_INTF_RX)
    {
        out->type = FIB_API_PATH_TYPE_INTERFACE_RX;
    }
    else
    {
        out->type = FIB_API_PATH_TYPE_NORMAL;
    }
    if (rpath->frp_flags & FIB_ROUTE_PATH_RESOLVE_VIA_HOST)
    {
        out->flags |= FIB_API_PATH_FLAG_RESOLVE_VIA_HOST;
    }
    if (rpath->frp_flags & FIB_ROUTE_PATH_RESOLVE_VIA_ATTACHED)
    {
        out->flags |= FIB_API_PATH_FLAG_RESOLVE_VIA_ATTACHED;
    }

    out->flags = htonl (out->flags);
    out->type = htonl (out->type);
    out->proto = htonl (out->proto);

    if (rpath->frp_label_stack)
    {
        int ii;

        for (ii = 0; ii < vec_len(rpath->frp_label_stack); ii++)
        {
            out->label_stack[ii].label =
                htonl(rpath->frp_label_stack[ii].fml_value);
            out->label_stack[ii].ttl =
                rpath->frp_label_stack[ii].fml_ttl;
            out->label_stack[ii].exp =
                rpath->frp_label_stack[ii].fml_exp;
        }
        out->n_labels = ii;
    }
}

int
fib_api_route_add_del (u8 is_add,
                       u8 is_multipath,
                       u32 fib_index,
                       const fib_prefix_t * prefix,
                       fib_source_t src,
                       fib_entry_flag_t entry_flags,
                       fib_route_path_t *rpaths)
{
    if (is_multipath)
    {
        if (vec_len(rpaths) == 0)
            return (VNET_API_ERROR_NO_PATHS_IN_ROUTE);

        /* Iterative path add/remove */
        if (is_add)
            fib_table_entry_path_add2 (fib_index,
                                       prefix,
                                       src,
                                       entry_flags,
                                       rpaths);
        else
            fib_table_entry_path_remove2 (fib_index,
                                          prefix,
                                          src,
                                          rpaths);
    }
    else
    {
        if (is_add)
        {
            if (vec_len(rpaths) == 0)
                return (VNET_API_ERROR_NO_PATHS_IN_ROUTE);

            /* path replacement */
            fib_table_entry_update (fib_index,
                                    prefix,
                                    src,
                                    entry_flags,
                                    rpaths);
        }
        else
            /* entry delete */
            fib_table_entry_delete (fib_index,
                                    prefix,
                                    src);
    }

    return (0);
}

u8*
format_vl_api_fib_path (u8 * s, va_list * args)
{
    const vl_api_fib_path_t *path = va_arg (*args, vl_api_fib_path_t*);

    s = format (s, "sw_if_index %d", ntohl (path->sw_if_index));
    switch (clib_net_to_host_u32(path->proto))
    {
    case FIB_API_PATH_NH_PROTO_IP4:
        s = format (s, " %U", format_vl_api_address_union,
                    &path->nh.address, ADDRESS_IP4);
        break;
    case FIB_API_PATH_NH_PROTO_IP6:
        s = format (s, " %U", format_vl_api_address_union,
                    &path->nh.address, ADDRESS_IP6);
        break;
    default:
        break;
    }
    s = format (s, " weight %d", path->weight);
    s = format (s, " preference %d", path->preference);
    s = format (s, " type %d", ntohl(path->type));
    s = format (s, " proto %d", ntohl(path->proto));
    s = format (s, " flags %d", ntohl(path->flags));
    s = format (s, " n_labels %d", ntohl(path->n_labels));
    s = format (s, " table-id %d", ntohl(path->table_id));
    s = format (s, " rpf-id %d", ntohl(path->rpf_id));

    return (s);
}

int
fib_proto_from_api_address_family (vl_api_address_family_t af, fib_protocol_t * out)
{
    switch (af)
    {
    case ADDRESS_IP4:
        *out = (FIB_PROTOCOL_IP4);
        return (0);
    case ADDRESS_IP6:
        *out = (FIB_PROTOCOL_IP6);
        return (0);
    }

    return (VNET_API_ERROR_INVALID_ADDRESS_FAMILY);
}

vl_api_address_family_t
fib_proto_to_api_address_family (fib_protocol_t fproto)
{
    switch (fproto)
    {
    case FIB_PROTOCOL_IP4:
        return (ADDRESS_IP4);
    case FIB_PROTOCOL_IP6:
        return (ADDRESS_IP6);
    default:
        break;
    }

    ASSERT(0);
    return (ADDRESS_IP4);
}

void
vl_api_fib_source_add_t_handler (vl_api_fib_source_add_t * mp)
{
    vl_api_fib_source_add_reply_t *rmp;
    fib_source_t src;
    int rv = 0;
    u8 *name;

    name = format (0, "%s", mp->src.name);
    vec_add1 (name, 0);

    src = fib_source_allocate((const char *)name,
                              mp->src.priority,
                              FIB_SOURCE_BH_API);

    vec_free(name);

    REPLY_MACRO2 (VL_API_FIB_SOURCE_ADD_REPLY,
    ({
        rmp->id = src;
    }));
}

typedef struct fib_source_dump_ctx_t_
{
    vl_api_registration_t * reg;
    u32 context;
} fib_source_dump_ctx_t;

static walk_rc_t
send_fib_source (fib_source_t id,
                 const char *name,
                 fib_source_priority_t prio,
                 fib_source_behaviour_t bh,
                 void *data)
{
    vl_api_fib_source_details_t *mp;
    fib_source_dump_ctx_t *ctx;

    ctx = data;
    mp = vl_msg_api_alloc_zero (sizeof (*mp));
    if (!mp)
        return WALK_STOP;

    mp->_vl_msg_id = ntohs (VL_API_FIB_SOURCE_DETAILS + REPLY_MSG_ID_BASE);
    mp->context = ctx->context;

    mp->src.priority = prio;
    mp->src.id = id;
    clib_memcpy(mp->src.name, name,
                clib_min(strlen(name), ARRAY_LEN(mp->src.name)));

    vl_api_send_msg (ctx->reg, (u8 *) mp);

    return (WALK_CONTINUE);
}

void
vl_api_fib_source_dump_t_handler (vl_api_fib_source_dump_t * mp)
{
    vl_api_registration_t *reg;

    reg = vl_api_client_index_to_registration (mp->client_index);
    if (!reg)
        return;

    fib_source_dump_ctx_t ctx = {
        .reg = reg,
        .context = mp->context,
    };

    fib_source_walk(send_fib_source, &ctx);
}


#include <vnet/fib/fib.api.c>

static clib_error_t *
fib_api_hookup (vlib_main_t * vm)
{
  /*
   * Set up the (msg_name, crc, message-id) table
   */
  fib_base_msg_id = setup_message_id_table ();

  return (NULL);
}

VLIB_API_INIT_FUNCTION (fib_api_hookup);