#include <stdio.h>
#include <stdint.h>
#include <errno.h>
#include <sys/socket.h>
 
#include "l3fwd.h"
#include "l3fwd_route.h"
 
static struct em_rule *em_route_base_v4;
static struct em_rule *em_route_base_v6;
 
enum {
    CB_FLD_DST_ADDR,
    CB_FLD_SRC_ADDR,
    CB_FLD_DST_PORT,
    CB_FLD_SRC_PORT,
    CB_FLD_PROTO,
    CB_FLD_IF_OUT,
    CB_FLD_MAX
};
 
static int
em_parse_v6_net(const char *in, uint8_t *v)
{
    int32_t rc;
 
    
    rc = inet_pton(AF_INET6, in, v);
    if (rc != 1)
        return -EINVAL;
 
    return 0;
}
 
static int
em_parse_v6_rule(char *str, struct em_rule *v)
{
    int i, rc;
    char *s, *sp, *in[CB_FLD_MAX];
    static const char *dlm = " \t\n";
    int dim = CB_FLD_MAX;
    s = str;
 
    for (i = 0; i != dim; i++, s = NULL) {
        in[i] = strtok_r(s, dlm, &sp);
        if (in[i] == NULL)
            return -EINVAL;
    }
 
    rc = em_parse_v6_net(in[CB_FLD_DST_ADDR], v->v6_key.ip_dst);
    if (rc != 0)
        return rc;
    rc = em_parse_v6_net(in[CB_FLD_SRC_ADDR], v->v6_key.ip_src);
    if (rc != 0)
        return rc;
 
    
    GET_CB_FIELD(in[CB_FLD_SRC_PORT], v->v6_key.port_src, 0, UINT16_MAX, 0);
    
    GET_CB_FIELD(in[CB_FLD_DST_PORT], v->v6_key.port_dst, 0, UINT16_MAX, 0);
    
    GET_CB_FIELD(in[CB_FLD_PROTO], v->v6_key.proto, 0, UINT8_MAX, 0);
    
    GET_CB_FIELD(in[CB_FLD_IF_OUT], v->if_out, 0, UINT8_MAX, 0);
 
    return 0;
}
 
static int
em_parse_v4_rule(char *str, struct em_rule *v)
{
    int i, rc;
    char *s, *sp, *in[CB_FLD_MAX];
    static const char *dlm = " \t\n";
    int dim = CB_FLD_MAX;
    s = str;
 
    for (i = 0; i != dim; i++, s = NULL) {
        in[i] = strtok_r(s, dlm, &sp);
        if (in[i] == NULL)
            return -EINVAL;
    }
 
    rc = inet_pton(AF_INET, in[CB_FLD_DST_ADDR], &(v->v4_key.ip_dst));
    v->v4_key.ip_dst = ntohl(v->v4_key.ip_dst);
    if (rc != 1)
        return rc;
 
    rc = inet_pton(AF_INET, in[CB_FLD_SRC_ADDR], &(v->v4_key.ip_src));
    v->v4_key.ip_src = ntohl(v->v4_key.ip_src);
    if (rc != 1)
        return rc;
 
    
    GET_CB_FIELD(in[CB_FLD_SRC_PORT], v->v4_key.port_src, 0, UINT16_MAX, 0);
    
    GET_CB_FIELD(in[CB_FLD_DST_PORT], v->v4_key.port_dst, 0, UINT16_MAX, 0);
    
    GET_CB_FIELD(in[CB_FLD_PROTO], v->v4_key.proto, 0, UINT8_MAX, 0);
    
    GET_CB_FIELD(in[CB_FLD_IF_OUT], v->if_out, 0, UINT8_MAX, 0);
 
    return 0;
}
 
static int
em_add_rules(const char *rule_path,
        struct em_rule **proute_base,
        int (*parser)(char *, struct em_rule *))
{
    struct em_rule *route_rules;
    struct em_rule *next;
    unsigned int route_num = 0;
    unsigned int route_cnt = 0;
    char buff[LINE_MAX];
    FILE *fh;
    unsigned int i = 0, rule_size = sizeof(*next);
    int val;
 
    *proute_base = NULL;
    fh = fopen(rule_path, "rb");
    if (fh == NULL)
        return -EINVAL;
 
    while ((fgets(buff, LINE_MAX, fh) != NULL)) {
        if (buff[0] == ROUTE_LEAD_CHAR)
            route_num++;
    }
 
    if (route_num == 0) {
        fclose(fh);
        return -EINVAL;
    }
 
    val = fseek(fh, 0, SEEK_SET);
    if (val < 0) {
        fclose(fh);
        return -EINVAL;
    }
 
    route_rules = calloc(route_num, rule_size);
 
    if (route_rules == NULL) {
        fclose(fh);
        return -EINVAL;
    }
 
    i = 0;
    while (fgets(buff, LINE_MAX, fh) != NULL) {
        i++;
        if (is_bypass_line(buff))
            continue;
 
        char s = buff[0];
 
        
        if (s == ROUTE_LEAD_CHAR)
            next = &route_rules[route_cnt];
 
        
        else {
                "%s Line %u: should start with leading "
                "char %c\n",
                rule_path, i, ROUTE_LEAD_CHAR);
            fclose(fh);
            free(route_rules);
            return -EINVAL;
        }
 
        if (parser(buff + 1, next) != 0) {
                "%s Line %u: parse rules error\n",
                rule_path, i);
            fclose(fh);
            free(route_rules);
            return -EINVAL;
        }
 
        route_cnt++;
    }
 
    fclose(fh);
 
    *proute_base = route_rules;
 
    return route_cnt;
}
 
static int
em_add_default_v4_rules(void)
{
    
    unsigned int i, rule_size = sizeof(*em_route_base_v4);
    route_num_v4 = 
RTE_DIM(ipv4_l3fwd_em_route_array);
 
 
    em_route_base_v4 = calloc(route_num_v4, rule_size);
 
    for (i = 0; i < (unsigned int)route_num_v4; i++) {
        em_route_base_v4[i].v4_key.ip_dst = ipv4_l3fwd_em_route_array[i].key.ip_dst;
        em_route_base_v4[i].v4_key.ip_src = ipv4_l3fwd_em_route_array[i].key.ip_src;
        em_route_base_v4[i].v4_key.port_dst = ipv4_l3fwd_em_route_array[i].key.port_dst;
        em_route_base_v4[i].v4_key.port_src = ipv4_l3fwd_em_route_array[i].key.port_src;
        em_route_base_v4[i].v4_key.proto = ipv4_l3fwd_em_route_array[i].key.proto;
        em_route_base_v4[i].if_out = ipv4_l3fwd_em_route_array[i].if_out;
    }
    return 0;
}
 
static int
em_add_default_v6_rules(void)
{
    
    unsigned int i, rule_size = sizeof(*em_route_base_v6);
    route_num_v6 = 
RTE_DIM(ipv6_l3fwd_em_route_array);
 
 
    em_route_base_v6 = calloc(route_num_v6, rule_size);
 
    for (i = 0; i < (unsigned int)route_num_v6; i++) {
        memcpy(em_route_base_v6[i].v6_key.ip_dst, ipv6_l3fwd_em_route_array[i].key.ip_dst,
               sizeof(em_route_base_v6[i].v6_key.ip_dst));
        memcpy(em_route_base_v6[i].v6_key.ip_src, ipv6_l3fwd_em_route_array[i].key.ip_src,
               sizeof(em_route_base_v6[i].v6_key.ip_src));
        em_route_base_v6[i].v6_key.port_dst = ipv6_l3fwd_em_route_array[i].key.port_dst;
        em_route_base_v6[i].v6_key.port_src = ipv6_l3fwd_em_route_array[i].key.port_src;
        em_route_base_v6[i].v6_key.proto = ipv6_l3fwd_em_route_array[i].key.proto;
        em_route_base_v6[i].if_out = ipv6_l3fwd_em_route_array[i].if_out;
    }
    return 0;
}
 
void
em_free_routes(void)
{
    free(em_route_base_v4);
    free(em_route_base_v6);
    em_route_base_v4 = NULL;
    em_route_base_v6 = NULL;
    route_num_v4 = 0;
    route_num_v6 = 0;
}
 
void
read_config_files_em(void)
{
    
    if (parm_config.rule_ipv4_name != NULL &&
            parm_config.rule_ipv6_name != NULL) {
        
        route_num_v4 = em_add_rules(parm_config.rule_ipv4_name,
                    &em_route_base_v4, &em_parse_v4_rule);
        if (route_num_v4 < 0) {
            em_free_routes();
            rte_exit(EXIT_FAILURE, 
"Failed to add EM IPv4 rules\n");
 
        }
 
        
        route_num_v6 = em_add_rules(parm_config.rule_ipv6_name,
                    &em_route_base_v6, &em_parse_v6_rule);
        if (route_num_v6 < 0) {
            em_free_routes();
            rte_exit(EXIT_FAILURE, 
"Failed to add EM IPv6 rules\n");
 
        }
    } else {
        RTE_LOG(INFO, L3FWD, 
"Missing 1 or more rule files, using default instead\n");
 
        if (em_add_default_v4_rules() < 0) {
            em_free_routes();
            rte_exit(EXIT_FAILURE, 
"Failed to add default IPv4 rules\n");
 
        }
        if (em_add_default_v6_rules() < 0) {
            em_free_routes();
            rte_exit(EXIT_FAILURE, 
"Failed to add default IPv6 rules\n");
 
        }
    }
}
__rte_noreturn void rte_exit(int exit_code, const char *format,...) __rte_format_printf(2
 
#define RTE_LOG(l, t,...)