/*
 * Copyright (c) 2008-2017 Nicira, Inc.
 *
 * 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 <config.h>
#include "openvswitch/ofp-bundle.h"
#include <errno.h>
#include <stdlib.h>
#include "openvswitch/ofp-parse.h"
#include "openvswitch/ofp-print.h"
#include "openvswitch/ofpbuf.h"
#include "openvswitch/vlog.h"
#include "util.h"

VLOG_DEFINE_THIS_MODULE(ofp_bundle);

/* Destroys 'bms'. */
void
ofputil_free_bundle_msgs(struct ofputil_bundle_msg *bms, size_t n_bms)
{
    for (size_t i = 0; i < n_bms; i++) {
        switch ((int)bms[i].type) {
        case OFPTYPE_FLOW_MOD:
            free(CONST_CAST(struct ofpact *, bms[i].fm.ofpacts));
            minimatch_destroy(&bms[i].fm.match);
            break;
        case OFPTYPE_GROUP_MOD:
            ofputil_uninit_group_mod(&bms[i].gm);
            break;
        case OFPTYPE_PACKET_OUT:
            free(bms[i].po.ofpacts);
            free(CONST_CAST(void *, bms[i].po.packet));
            break;
        default:
            break;
        }
    }
    free(bms);
}

void
ofputil_encode_bundle_msgs(const struct ofputil_bundle_msg *bms,
                           size_t n_bms, struct ovs_list *requests,
                           enum ofputil_protocol protocol)
{
    enum ofp_version version = ofputil_protocol_to_ofp_version(protocol);

    for (size_t i = 0; i < n_bms; i++) {
        struct ofpbuf *request = NULL;

        switch ((int)bms[i].type) {
        case OFPTYPE_FLOW_MOD:
            request = ofputil_encode_flow_mod(&bms[i].fm, protocol);
            break;
        case OFPTYPE_GROUP_MOD:
            request = ofputil_encode_group_mod(version, &bms[i].gm, NULL, -1);
            break;
        case OFPTYPE_PACKET_OUT:
            request = ofputil_encode_packet_out(&bms[i].po, protocol);
            break;
        default:
            break;
        }
        if (request) {
            ovs_list_push_back(requests, &request->list_node);
        }
    }
}

enum ofperr
ofputil_decode_bundle_ctrl(const struct ofp_header *oh,
                           struct ofputil_bundle_ctrl_msg *msg)
{
    struct ofpbuf b = ofpbuf_const_initializer(oh, ntohs(oh->length));
    enum ofpraw raw = ofpraw_pull_assert(&b);
    ovs_assert(raw == OFPRAW_OFPT14_BUNDLE_CONTROL
               || raw == OFPRAW_ONFT13_BUNDLE_CONTROL);

    const struct ofp14_bundle_ctrl_msg *m = b.msg;
    msg->bundle_id = ntohl(m->bundle_id);
    msg->type = ntohs(m->type);
    msg->flags = ntohs(m->flags);

    return 0;
}

struct ofpbuf *
ofputil_encode_bundle_ctrl_request(enum ofp_version ofp_version,
                                   struct ofputil_bundle_ctrl_msg *bc)
{
    struct ofpbuf *request;
    struct ofp14_bundle_ctrl_msg *m;

    switch (ofp_version) {
    case OFP10_VERSION:
    case OFP11_VERSION:
    case OFP12_VERSION:
        ovs_fatal(0, "bundles need OpenFlow 1.3 or later "
                     "(\'-O OpenFlow14\')");
    case OFP13_VERSION:
    case OFP14_VERSION:
    case OFP15_VERSION:
        request = ofpraw_alloc(ofp_version == OFP13_VERSION
                               ? OFPRAW_ONFT13_BUNDLE_CONTROL
                               : OFPRAW_OFPT14_BUNDLE_CONTROL, ofp_version, 0);
        m = ofpbuf_put_zeros(request, sizeof *m);

        m->bundle_id = htonl(bc->bundle_id);
        m->type = htons(bc->type);
        m->flags = htons(bc->flags);
        break;
    default:
        OVS_NOT_REACHED();
    }

    return request;
}

static const char *
bundle_flags_to_name(uint32_t bit)
{
    switch (bit) {
    case OFPBF_ATOMIC:
        return "atomic";
    case OFPBF_ORDERED:
        return "ordered";
    default:
        return NULL;
    }
}

void
ofputil_format_bundle_ctrl_request(struct ds *s,
                                   const struct ofputil_bundle_ctrl_msg *bctrl)
{
    ds_put_char(s, '\n');
    ds_put_format(s, " bundle_id=%#"PRIx32" type=",  bctrl->bundle_id);
    switch (bctrl->type) {
    case OFPBCT_OPEN_REQUEST:
        ds_put_cstr(s, "OPEN_REQUEST");
        break;
    case OFPBCT_OPEN_REPLY:
        ds_put_cstr(s, "OPEN_REPLY");
        break;
    case OFPBCT_CLOSE_REQUEST:
        ds_put_cstr(s, "CLOSE_REQUEST");
        break;
    case OFPBCT_CLOSE_REPLY:
        ds_put_cstr(s, "CLOSE_REPLY");
        break;
    case OFPBCT_COMMIT_REQUEST:
        ds_put_cstr(s, "COMMIT_REQUEST");
        break;
    case OFPBCT_COMMIT_REPLY:
        ds_put_cstr(s, "COMMIT_REPLY");
        break;
    case OFPBCT_DISCARD_REQUEST:
        ds_put_cstr(s, "DISCARD_REQUEST");
        break;
    case OFPBCT_DISCARD_REPLY:
        ds_put_cstr(s, "DISCARD_REPLY");
        break;
    }

    ds_put_cstr(s, " flags=");
    ofp_print_bit_names(s, bctrl->flags, bundle_flags_to_name, ' ');
}


struct ofpbuf *
ofputil_encode_bundle_ctrl_reply(const struct ofp_header *oh,
                                 struct ofputil_bundle_ctrl_msg *msg)
{
    struct ofpbuf *buf;
    struct ofp14_bundle_ctrl_msg *m;

    buf = ofpraw_alloc_reply(oh->version == OFP13_VERSION
                             ? OFPRAW_ONFT13_BUNDLE_CONTROL
                             : OFPRAW_OFPT14_BUNDLE_CONTROL, oh, 0);
    m = ofpbuf_put_zeros(buf, sizeof *m);

    m->bundle_id = htonl(msg->bundle_id);
    m->type = htons(msg->type);
    m->flags = htons(msg->flags);

    return buf;
}

/* Return true for bundlable state change requests, false for other messages.
 */
static bool
ofputil_is_bundlable(enum ofptype type)
{
    switch (type) {
        /* Minimum required by OpenFlow 1.4. */
    case OFPTYPE_PORT_MOD:
    case OFPTYPE_FLOW_MOD:
        /* Other supported types. */
    case OFPTYPE_GROUP_MOD:
    case OFPTYPE_PACKET_OUT:
        return true;

        /* Nice to have later. */
    case OFPTYPE_FLOW_MOD_TABLE_ID:
    case OFPTYPE_TABLE_MOD:
    case OFPTYPE_METER_MOD:
    case OFPTYPE_NXT_TLV_TABLE_MOD:

        /* Not to be bundlable. */
    case OFPTYPE_ECHO_REQUEST:
    case OFPTYPE_FEATURES_REQUEST:
    case OFPTYPE_GET_CONFIG_REQUEST:
    case OFPTYPE_SET_CONFIG:
    case OFPTYPE_BARRIER_REQUEST:
    case OFPTYPE_ROLE_REQUEST:
    case OFPTYPE_ECHO_REPLY:
    case OFPTYPE_SET_FLOW_FORMAT:
    case OFPTYPE_SET_PACKET_IN_FORMAT:
    case OFPTYPE_SET_CONTROLLER_ID:
    case OFPTYPE_FLOW_AGE:
    case OFPTYPE_FLOW_MONITOR_CANCEL:
    case OFPTYPE_SET_ASYNC_CONFIG:
    case OFPTYPE_GET_ASYNC_REQUEST:
    case OFPTYPE_DESC_STATS_REQUEST:
    case OFPTYPE_FLOW_STATS_REQUEST:
    case OFPTYPE_AGGREGATE_STATS_REQUEST:
    case OFPTYPE_TABLE_STATS_REQUEST:
    case OFPTYPE_TABLE_FEATURES_STATS_REQUEST:
    case OFPTYPE_TABLE_DESC_REQUEST:
    case OFPTYPE_PORT_STATS_REQUEST:
    case OFPTYPE_QUEUE_STATS_REQUEST:
    case OFPTYPE_PORT_DESC_STATS_REQUEST:
    case OFPTYPE_FLOW_MONITOR_STATS_REQUEST:
    case OFPTYPE_METER_STATS_REQUEST:
    case OFPTYPE_METER_CONFIG_STATS_REQUEST:
    case OFPTYPE_METER_FEATURES_STATS_REQUEST:
    case OFPTYPE_GROUP_STATS_REQUEST:
    case OFPTYPE_GROUP_DESC_STATS_REQUEST:
    case OFPTYPE_GROUP_FEATURES_STATS_REQUEST:
    case OFPTYPE_QUEUE_GET_CONFIG_REQUEST:
    case OFPTYPE_BUNDLE_CONTROL:
    case OFPTYPE_BUNDLE_ADD_MESSAGE:
    case OFPTYPE_HELLO:
    case OFPTYPE_ERROR:
    case OFPTYPE_FEATURES_REPLY:
    case OFPTYPE_GET_CONFIG_REPLY:
    case OFPTYPE_PACKET_IN:
    case OFPTYPE_FLOW_REMOVED:
    case OFPTYPE_PORT_STATUS:
    case OFPTYPE_BARRIER_REPLY:
    case OFPTYPE_QUEUE_GET_CONFIG_REPLY:
    case OFPTYPE_DESC_STATS_REPLY:
    case OFPTYPE_FLOW_STATS_REPLY:
    case OFPTYPE_QUEUE_STATS_REPLY:
    case OFPTYPE_PORT_STATS_REPLY:
    case OFPTYPE_TABLE_STATS_REPLY:
    case OFPTYPE_AGGREGATE_STATS_REPLY:
    case OFPTYPE_PORT_DESC_STATS_REPLY:
    case OFPTYPE_ROLE_REPLY:
    case OFPTYPE_FLOW_MONITOR_PAUSED:
    case OFPTYPE_FLOW_MONITOR_RESUMED:
    case OFPTYPE_FLOW_MONITOR_STATS_REPLY:
    case OFPTYPE_GET_ASYNC_REPLY:
    case OFPTYPE_GROUP_STATS_REPLY:
    case OFPTYPE_GROUP_DESC_STATS_REPLY:
    case OFPTYPE_GROUP_FEATURES_STATS_REPLY:
    case OFPTYPE_METER_STATS_REPLY:
    case OFPTYPE_METER_CONFIG_STATS_REPLY:
    case OFPTYPE_METER_FEATURES_STATS_REPLY:
    case OFPTYPE_TABLE_FEATURES_STATS_REPLY:
    case OFPTYPE_TABLE_DESC_REPLY:
    case OFPTYPE_ROLE_STATUS:
    case OFPTYPE_REQUESTFORWARD:
    case OFPTYPE_TABLE_STATUS:
    case OFPTYPE_NXT_TLV_TABLE_REQUEST:
    case OFPTYPE_NXT_TLV_TABLE_REPLY:
    case OFPTYPE_NXT_RESUME:
    case OFPTYPE_IPFIX_BRIDGE_STATS_REQUEST:
    case OFPTYPE_IPFIX_BRIDGE_STATS_REPLY:
    case OFPTYPE_IPFIX_FLOW_STATS_REQUEST:
    case OFPTYPE_IPFIX_FLOW_STATS_REPLY:
    case OFPTYPE_CT_FLUSH_ZONE:
    case OFPTYPE_CT_FLUSH:
        break;
    }

    return false;
}

enum ofperr
ofputil_decode_bundle_add(const struct ofp_header *oh,
                          struct ofputil_bundle_add_msg *msg,
                          enum ofptype *typep)
{
    struct ofpbuf b = ofpbuf_const_initializer(oh, ntohs(oh->length));

    /* Pull the outer ofp_header. */
    enum ofpraw raw = ofpraw_pull_assert(&b);
    ovs_assert(raw == OFPRAW_OFPT14_BUNDLE_ADD_MESSAGE
               || raw == OFPRAW_ONFT13_BUNDLE_ADD_MESSAGE);

    /* Pull the bundle_ctrl header. */
    const struct ofp14_bundle_ctrl_msg *m = ofpbuf_pull(&b, sizeof *m);
    msg->bundle_id = ntohl(m->bundle_id);
    msg->flags = ntohs(m->flags);

    /* Pull the inner ofp_header. */
    if (b.size < sizeof(struct ofp_header)) {
        return OFPERR_OFPBFC_MSG_BAD_LEN;
    }
    msg->msg = b.data;
    if (msg->msg->version != oh->version) {
        return OFPERR_OFPBFC_BAD_VERSION;
    }
    size_t inner_len = ntohs(msg->msg->length);
    if (inner_len < sizeof(struct ofp_header) || inner_len > b.size) {
        return OFPERR_OFPBFC_MSG_BAD_LEN;
    }
    if (msg->msg->xid != oh->xid) {
        return OFPERR_OFPBFC_MSG_BAD_XID;
    }

    /* Reject unbundlable messages. */
    enum ofptype type;
    enum ofperr error = ofptype_decode(&type, msg->msg);
    if (error) {
        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
        VLOG_WARN_RL(&rl, "OFPT14_BUNDLE_ADD_MESSAGE contained "
                     "message is unparsable (%s)", ofperr_get_name(error));
        return OFPERR_OFPBFC_MSG_UNSUP; /* 'error' would be confusing. */
    }

    if (!ofputil_is_bundlable(type)) {
        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
        VLOG_WARN_RL(&rl, "%s message not allowed inside "
                     "OFPT14_BUNDLE_ADD_MESSAGE", ofptype_get_name(type));
        return OFPERR_OFPBFC_MSG_UNSUP;
    }
    if (typep) {
        *typep = type;
    }

    return 0;
}

struct ofpbuf *
ofputil_encode_bundle_add(enum ofp_version ofp_version,
                          struct ofputil_bundle_add_msg *msg)
{
    struct ofpbuf *request;
    struct ofp14_bundle_ctrl_msg *m;

    /* Must use the same xid as the embedded message. */
    request = ofpraw_alloc_xid(ofp_version == OFP13_VERSION
                               ? OFPRAW_ONFT13_BUNDLE_ADD_MESSAGE
                               : OFPRAW_OFPT14_BUNDLE_ADD_MESSAGE, ofp_version,
                               msg->msg->xid, ntohs(msg->msg->length));
    m = ofpbuf_put_zeros(request, sizeof *m);

    m->bundle_id = htonl(msg->bundle_id);
    m->flags = htons(msg->flags);
    ofpbuf_put(request, msg->msg, ntohs(msg->msg->length));

    ofpmsg_update_length(request);
    return request;
}

/* Opens file 'file_name' and reads each line as a flow_mod or a group_mod,
 * depending on the first keyword on each line.  Stores each flow and group
 * mods in '*bms', an array allocated on the caller's behalf, and the number of
 * messages in '*n_bms'.
 *
 * Returns NULL if successful, otherwise a malloc()'d string describing the
 * error.  The caller is responsible for freeing the returned string. */
char * OVS_WARN_UNUSED_RESULT
parse_ofp_bundle_file(const char *file_name,
                      const struct ofputil_port_map *port_map,
                      const struct ofputil_table_map *table_map,
                      struct ofputil_bundle_msg **bms, size_t *n_bms,
                      enum ofputil_protocol *usable_protocols)
{
    size_t allocated_bms;
    char *error = NULL;
    int line_number;
    FILE *stream;
    struct ds ds;

    *usable_protocols = OFPUTIL_P_ANY;

    *bms = NULL;
    *n_bms = 0;

    stream = !strcmp(file_name, "-") ? stdin : fopen(file_name, "r");
    if (stream == NULL) {
        return xasprintf("%s: open failed (%s)",
                         file_name, ovs_strerror(errno));
    }

    allocated_bms = *n_bms;
    ds_init(&ds);
    line_number = 0;
    while (!ds_get_preprocessed_line(&ds, stream, &line_number)) {
        enum ofputil_protocol usable;
        char *s = ds_cstr(&ds);
        size_t len;

        if (*n_bms >= allocated_bms) {
            struct ofputil_bundle_msg *new_bms;

            new_bms = x2nrealloc(*bms, &allocated_bms, sizeof **bms);
            for (size_t i = 0; i < *n_bms; i++) {
                if (new_bms[i].type == OFPTYPE_GROUP_MOD) {
                    ovs_list_moved(&new_bms[i].gm.buckets,
                                   &(*bms)[i].gm.buckets);
                }
            }
            *bms = new_bms;
        }

        s += strspn(s, " \t\r\n");   /* Skip white space. */
        len = strcspn(s, ", \t\r\n"); /* Get length of the first token. */

        if (!strncmp(s, "flow", len)) {
            s += len;
            error = parse_ofp_flow_mod_str(&(*bms)[*n_bms].fm, s, port_map,
                                           table_map, -2, &usable);
            if (error) {
                break;
            }
            (*bms)[*n_bms].type = OFPTYPE_FLOW_MOD;
        } else if (!strncmp(s, "group", len)) {
            s += len;
            error = parse_ofp_group_mod_str(&(*bms)[*n_bms].gm, -2, s,
                                            port_map, table_map, &usable);
            if (error) {
                break;
            }
            (*bms)[*n_bms].type = OFPTYPE_GROUP_MOD;
        } else if (!strncmp(s, "packet-out", len)) {
            s += len;
            error = parse_ofp_packet_out_str(&(*bms)[*n_bms].po, s, port_map,
                                             table_map, &usable);
            if (error) {
                break;
            }
            (*bms)[*n_bms].type = OFPTYPE_PACKET_OUT;
        } else {
            error = xasprintf("Unsupported bundle message type: %.*s",
                              (int)len, s);
            break;
        }

        *usable_protocols &= usable; /* Each line can narrow the set. */
        *n_bms += 1;
    }

    ds_destroy(&ds);
    if (stream != stdin) {
        fclose(stream);
    }

    if (error) {
        char *err_msg = xasprintf("%s:%d: %s", file_name, line_number, error);
        free(error);

        ofputil_free_bundle_msgs(*bms, *n_bms);
        *bms = NULL;
        *n_bms = 0;
        return err_msg;
    }
    return NULL;
}

void
ofputil_format_bundle_add(struct ds *s,
                          const struct ofputil_bundle_add_msg *badd,
                          const struct ofputil_port_map *port_map,
                          const struct ofputil_table_map *table_map,
                          int verbosity)
{
    ds_put_char(s, '\n');
    ds_put_format(s, " bundle_id=%#"PRIx32, badd->bundle_id);
    ds_put_cstr(s, " flags=");
    ofp_print_bit_names(s, badd->flags, bundle_flags_to_name, ' ');

    ds_put_char(s, '\n');
    char *msg = ofp_to_string(badd->msg, ntohs(badd->msg->length), port_map,
                              table_map, verbosity);
    ds_put_and_free_cstr(s, msg);
}
