+ case OFP14_VERSION:
+ OVS_NOT_REACHED();
+ break;
+
+ default:
+ OVS_NOT_REACHED();
+ }
+}
+
+/* Frees all of the "struct ofputil_bucket"s in the 'buckets' list. */
+void
+ofputil_bucket_list_destroy(struct list *buckets)
+{
+ struct ofputil_bucket *bucket, *next_bucket;
+
+ LIST_FOR_EACH_SAFE (bucket, next_bucket, list_node, buckets) {
+ list_remove(&bucket->list_node);
+ free(bucket->ofpacts);
+ free(bucket);
+ }
+}
+
+/* Returns an OpenFlow group stats request for OpenFlow version 'ofp_version',
+ * that requests stats for group 'group_id'. (Use OFPG_ALL to request stats
+ * for all groups.)
+ *
+ * Group statistics include packet and byte counts for each group. */
+struct ofpbuf *
+ofputil_encode_group_stats_request(enum ofp_version ofp_version,
+ uint32_t group_id)
+{
+ struct ofpbuf *request;
+
+ switch (ofp_version) {
+ case OFP10_VERSION:
+ ovs_fatal(0, "dump-group-stats needs OpenFlow 1.1 or later "
+ "(\'-O OpenFlow11\')");
+ case OFP11_VERSION:
+ case OFP12_VERSION:
+ case OFP13_VERSION:
+ case OFP14_VERSION: {
+ struct ofp11_group_stats_request *req;
+ request = ofpraw_alloc(OFPRAW_OFPST11_GROUP_REQUEST, ofp_version, 0);
+ req = ofpbuf_put_zeros(request, sizeof *req);
+ req->group_id = htonl(group_id);
+ break;
+ }
+ default:
+ OVS_NOT_REACHED();
+ }
+
+ return request;
+}
+
+/* Returns an OpenFlow group description request for OpenFlow version
+ * 'ofp_version', that requests stats for group 'group_id'. (Use OFPG_ALL to
+ * request stats for all groups.)
+ *
+ * Group descriptions include the bucket and action configuration for each
+ * group. */
+struct ofpbuf *
+ofputil_encode_group_desc_request(enum ofp_version ofp_version)
+{
+ struct ofpbuf *request;
+
+ switch (ofp_version) {
+ case OFP10_VERSION:
+ ovs_fatal(0, "dump-groups needs OpenFlow 1.1 or later "
+ "(\'-O OpenFlow11\')");
+ case OFP11_VERSION:
+ case OFP12_VERSION:
+ case OFP13_VERSION:
+ case OFP14_VERSION:
+ request = ofpraw_alloc(OFPRAW_OFPST11_GROUP_DESC_REQUEST, ofp_version, 0);
+ break;
+ default:
+ OVS_NOT_REACHED();
+ }
+
+ return request;
+}
+
+static void
+ofputil_group_stats_to_ofp11__(const struct ofputil_group_stats *gs,
+ struct ofp11_group_stats *gs11, size_t length,
+ struct ofp11_bucket_counter bucket_cnts[])
+{
+ int i;
+
+ memset(gs11, 0, length);
+ gs11->length = htons(length);
+ gs11->group_id = htonl(gs->group_id);
+ gs11->ref_count = htonl(gs->ref_count);
+ gs11->packet_count = htonll(gs->packet_count);
+ gs11->byte_count = htonll(gs->byte_count);
+
+ for (i = 0; i < gs->n_buckets; i++) {
+ bucket_cnts[i].packet_count = htonll(gs->bucket_stats[i].packet_count);
+ bucket_cnts[i].byte_count = htonll(gs->bucket_stats[i].byte_count);
+ }
+}
+
+static void
+ofputil_group_stats_to_ofp11(const struct ofputil_group_stats *gs,
+ struct ofp11_group_stats *gs11, size_t length)
+{
+ ofputil_group_stats_to_ofp11__(gs, gs11, length, gs11->bucket_stats);
+}
+
+static void
+ofputil_group_stats_to_ofp13(const struct ofputil_group_stats *gs,
+ struct ofp13_group_stats *gs13, size_t length)
+{
+ ofputil_group_stats_to_ofp11__(gs, &gs13->gs, length, gs13->bucket_stats);
+ gs13->duration_sec = htonl(gs->duration_sec);
+ gs13->duration_nsec = htonl(gs->duration_nsec);
+}
+
+/* Encodes 'gs' properly for the format of the list of group statistics
+ * replies already begun in 'replies' and appends it to the list. 'replies'
+ * must have originally been initialized with ofpmp_init(). */
+void
+ofputil_append_group_stats(struct list *replies,
+ const struct ofputil_group_stats *gs)
+{
+ struct ofpbuf *msg = ofpbuf_from_list(list_back(replies));
+ struct ofp_header *oh = ofpbuf_data(msg);
+ size_t length;
+
+ switch ((enum ofp_version) oh->version) {
+ case OFP11_VERSION:
+ case OFP12_VERSION:{
+ struct ofp11_group_stats *reply;
+
+ length = gs->n_buckets * sizeof reply->bucket_stats[0]
+ + sizeof *reply;
+ reply = ofpmp_append(replies, length);
+ ofputil_group_stats_to_ofp11(gs, reply, length);
+ break;
+ }
+
+ case OFP13_VERSION:
+ case OFP14_VERSION:{
+ struct ofp13_group_stats *reply;
+
+ length = gs->n_buckets * sizeof reply->bucket_stats[0]
+ + sizeof *reply;
+ reply = ofpmp_append(replies, length);
+ ofputil_group_stats_to_ofp13(gs, reply, length);
+ break;
+ }
+
+ case OFP10_VERSION:
+ default:
+ OVS_NOT_REACHED();
+ }
+}
+/* Returns an OpenFlow group features request for OpenFlow version
+ * 'ofp_version'. */
+struct ofpbuf *
+ofputil_encode_group_features_request(enum ofp_version ofp_version)
+{
+ struct ofpbuf *request = NULL;
+
+ switch (ofp_version) {
+ case OFP10_VERSION:
+ case OFP11_VERSION:
+ ovs_fatal(0, "dump-group-features needs OpenFlow 1.2 or later "
+ "(\'-O OpenFlow12\')");
+ case OFP12_VERSION:
+ case OFP13_VERSION:
+ case OFP14_VERSION:
+ request = ofpraw_alloc(OFPRAW_OFPST12_GROUP_FEATURES_REQUEST,
+ ofp_version, 0);
+ break;
+ default:
+ OVS_NOT_REACHED();
+ }
+
+ return request;
+}
+
+/* Returns a OpenFlow message that encodes 'features' properly as a reply to
+ * group features request 'request'. */
+struct ofpbuf *
+ofputil_encode_group_features_reply(
+ const struct ofputil_group_features *features,
+ const struct ofp_header *request)
+{
+ struct ofp12_group_features_stats *ogf;
+ struct ofpbuf *reply;
+
+ reply = ofpraw_alloc_xid(OFPRAW_OFPST12_GROUP_FEATURES_REPLY,
+ request->version, request->xid, 0);
+ ogf = ofpbuf_put_zeros(reply, sizeof *ogf);
+ ogf->types = htonl(features->types);
+ ogf->capabilities = htonl(features->capabilities);
+ ogf->max_groups[0] = htonl(features->max_groups[0]);
+ ogf->max_groups[1] = htonl(features->max_groups[1]);
+ ogf->max_groups[2] = htonl(features->max_groups[2]);
+ ogf->max_groups[3] = htonl(features->max_groups[3]);
+ ogf->actions[0] = htonl(features->actions[0]);
+ ogf->actions[1] = htonl(features->actions[1]);
+ ogf->actions[2] = htonl(features->actions[2]);
+ ogf->actions[3] = htonl(features->actions[3]);
+
+ return reply;
+}
+
+/* Decodes group features reply 'oh' into 'features'. */
+void
+ofputil_decode_group_features_reply(const struct ofp_header *oh,
+ struct ofputil_group_features *features)
+{
+ const struct ofp12_group_features_stats *ogf = ofpmsg_body(oh);
+
+ features->types = ntohl(ogf->types);
+ features->capabilities = ntohl(ogf->capabilities);
+ features->max_groups[0] = ntohl(ogf->max_groups[0]);
+ features->max_groups[1] = ntohl(ogf->max_groups[1]);
+ features->max_groups[2] = ntohl(ogf->max_groups[2]);
+ features->max_groups[3] = ntohl(ogf->max_groups[3]);
+ features->actions[0] = ntohl(ogf->actions[0]);
+ features->actions[1] = ntohl(ogf->actions[1]);
+ features->actions[2] = ntohl(ogf->actions[2]);
+ features->actions[3] = ntohl(ogf->actions[3]);
+}
+
+/* Parse a group status request message into a 32 bit OpenFlow 1.1
+ * group ID and stores the latter in '*group_id'.
+ * Returns 0 if successful, otherwise an OFPERR_* number. */
+enum ofperr
+ofputil_decode_group_stats_request(const struct ofp_header *request,
+ uint32_t *group_id)
+{
+ const struct ofp11_group_stats_request *gsr11 = ofpmsg_body(request);
+ *group_id = ntohl(gsr11->group_id);
+ return 0;
+}
+
+/* Converts a group stats reply in 'msg' into an abstract ofputil_group_stats
+ * in 'gs'. Assigns freshly allocated memory to gs->bucket_stats for the
+ * caller to eventually free.
+ *
+ * Multiple group stats replies can be packed into a single OpenFlow message.
+ * Calling this function multiple times for a single 'msg' iterates through the
+ * replies. The caller must initially leave 'msg''s layer pointers null and
+ * not modify them between calls.
+ *
+ * Returns 0 if successful, EOF if no replies were left in this 'msg',
+ * otherwise a positive errno value. */
+int
+ofputil_decode_group_stats_reply(struct ofpbuf *msg,
+ struct ofputil_group_stats *gs)
+{
+ struct ofp11_bucket_counter *obc;
+ struct ofp11_group_stats *ogs11;
+ enum ofpraw raw;
+ enum ofperr error;
+ size_t base_len;
+ size_t length;
+ size_t i;
+
+ gs->bucket_stats = NULL;
+ error = (msg->frame
+ ? ofpraw_decode(&raw, msg->frame)
+ : ofpraw_pull(&raw, msg));
+ if (error) {
+ return error;
+ }
+
+ if (!ofpbuf_size(msg)) {
+ return EOF;
+ }
+
+ if (raw == OFPRAW_OFPST11_GROUP_REPLY) {
+ base_len = sizeof *ogs11;
+ ogs11 = ofpbuf_try_pull(msg, sizeof *ogs11);
+ gs->duration_sec = gs->duration_nsec = UINT32_MAX;
+ } else if (raw == OFPRAW_OFPST13_GROUP_REPLY) {
+ struct ofp13_group_stats *ogs13;
+
+ base_len = sizeof *ogs13;
+ ogs13 = ofpbuf_try_pull(msg, sizeof *ogs13);
+ if (ogs13) {
+ ogs11 = &ogs13->gs;
+ gs->duration_sec = ntohl(ogs13->duration_sec);
+ gs->duration_nsec = ntohl(ogs13->duration_nsec);
+ } else {
+ ogs11 = NULL;
+ }
+ } else {
+ OVS_NOT_REACHED();
+ }
+
+ if (!ogs11) {
+ VLOG_WARN_RL(&bad_ofmsg_rl, "%s reply has %"PRIu32" leftover bytes at end",
+ ofpraw_get_name(raw), ofpbuf_size(msg));
+ return OFPERR_OFPBRC_BAD_LEN;
+ }
+ length = ntohs(ogs11->length);
+ if (length < sizeof base_len) {
+ VLOG_WARN_RL(&bad_ofmsg_rl, "%s reply claims invalid length %"PRIuSIZE,
+ ofpraw_get_name(raw), length);
+ return OFPERR_OFPBRC_BAD_LEN;
+ }
+
+ gs->group_id = ntohl(ogs11->group_id);
+ gs->ref_count = ntohl(ogs11->ref_count);
+ gs->packet_count = ntohll(ogs11->packet_count);
+ gs->byte_count = ntohll(ogs11->byte_count);
+
+ gs->n_buckets = (length - base_len) / sizeof *obc;
+ obc = ofpbuf_try_pull(msg, gs->n_buckets * sizeof *obc);
+ if (!obc) {
+ VLOG_WARN_RL(&bad_ofmsg_rl, "%s reply has %"PRIu32" leftover bytes at end",
+ ofpraw_get_name(raw), ofpbuf_size(msg));
+ return OFPERR_OFPBRC_BAD_LEN;
+ }
+
+ gs->bucket_stats = xmalloc(gs->n_buckets * sizeof *gs->bucket_stats);
+ for (i = 0; i < gs->n_buckets; i++) {
+ gs->bucket_stats[i].packet_count = ntohll(obc[i].packet_count);
+ gs->bucket_stats[i].byte_count = ntohll(obc[i].byte_count);
+ }
+
+ return 0;
+}
+
+/* Appends a group stats reply that contains the data in 'gds' to those already
+ * present in the list of ofpbufs in 'replies'. 'replies' should have been
+ * initialized with ofpmp_init(). */
+void
+ofputil_append_group_desc_reply(const struct ofputil_group_desc *gds,
+ struct list *buckets,
+ struct list *replies)
+{
+ struct ofpbuf *reply = ofpbuf_from_list(list_back(replies));
+ struct ofp11_group_desc_stats *ogds;
+ struct ofputil_bucket *bucket;
+ size_t start_ogds;
+ enum ofp_version version = ((struct ofp_header *)ofpbuf_data(reply))->version;
+
+ start_ogds = ofpbuf_size(reply);
+ ofpbuf_put_zeros(reply, sizeof *ogds);
+ LIST_FOR_EACH (bucket, list_node, buckets) {
+ struct ofp11_bucket *ob;
+ size_t start_ob;
+
+ start_ob = ofpbuf_size(reply);
+ ofpbuf_put_zeros(reply, sizeof *ob);
+ ofpacts_put_openflow_actions(bucket->ofpacts, bucket->ofpacts_len,
+ reply, version);
+ ob = ofpbuf_at_assert(reply, start_ob, sizeof *ob);
+ ob->len = htons(ofpbuf_size(reply) - start_ob);
+ ob->weight = htons(bucket->weight);
+ ob->watch_port = ofputil_port_to_ofp11(bucket->watch_port);
+ ob->watch_group = htonl(bucket->watch_group);
+ }
+ ogds = ofpbuf_at_assert(reply, start_ogds, sizeof *ogds);
+ ogds->length = htons(ofpbuf_size(reply) - start_ogds);
+ ogds->type = gds->type;
+ ogds->group_id = htonl(gds->group_id);
+
+ ofpmp_postappend(replies, start_ogds);
+}
+
+static enum ofperr
+ofputil_pull_buckets(struct ofpbuf *msg, size_t buckets_length,
+ enum ofp_version version, struct list *buckets)
+{
+ struct ofp11_bucket *ob;
+
+ list_init(buckets);
+ while (buckets_length > 0) {
+ struct ofputil_bucket *bucket;
+ struct ofpbuf ofpacts;
+ enum ofperr error;
+ size_t ob_len;
+
+ ob = (buckets_length >= sizeof *ob
+ ? ofpbuf_try_pull(msg, sizeof *ob)
+ : NULL);
+ if (!ob) {
+ VLOG_WARN_RL(&bad_ofmsg_rl, "buckets end with %"PRIuSIZE" leftover bytes",
+ buckets_length);
+ }
+
+ ob_len = ntohs(ob->len);
+ if (ob_len < sizeof *ob) {
+ VLOG_WARN_RL(&bad_ofmsg_rl, "OpenFlow message bucket length "
+ "%"PRIuSIZE" is not valid", ob_len);
+ return OFPERR_OFPGMFC_BAD_BUCKET;
+ } else if (ob_len > buckets_length) {
+ VLOG_WARN_RL(&bad_ofmsg_rl, "OpenFlow message bucket length "
+ "%"PRIuSIZE" exceeds remaining buckets data size %"PRIuSIZE,
+ ob_len, buckets_length);
+ return OFPERR_OFPGMFC_BAD_BUCKET;
+ }
+ buckets_length -= ob_len;
+
+ ofpbuf_init(&ofpacts, 0);
+ error = ofpacts_pull_openflow_actions(msg, ob_len - sizeof *ob,
+ version, &ofpacts);
+ if (error) {
+ ofpbuf_uninit(&ofpacts);
+ ofputil_bucket_list_destroy(buckets);
+ return error;
+ }
+
+ bucket = xzalloc(sizeof *bucket);
+ bucket->weight = ntohs(ob->weight);
+ error = ofputil_port_from_ofp11(ob->watch_port, &bucket->watch_port);
+ if (error) {
+ ofpbuf_uninit(&ofpacts);
+ ofputil_bucket_list_destroy(buckets);
+ return OFPERR_OFPGMFC_BAD_WATCH;
+ }
+ bucket->watch_group = ntohl(ob->watch_group);
+ bucket->ofpacts = ofpbuf_steal_data(&ofpacts);
+ bucket->ofpacts_len = ofpbuf_size(&ofpacts);
+ list_push_back(buckets, &bucket->list_node);
+ }
+
+ return 0;
+}
+
+/* Converts a group description reply in 'msg' into an abstract
+ * ofputil_group_desc in 'gd'.
+ *
+ * Multiple group description replies can be packed into a single OpenFlow
+ * message. Calling this function multiple times for a single 'msg' iterates
+ * through the replies. The caller must initially leave 'msg''s layer pointers
+ * null and not modify them between calls.
+ *
+ * Returns 0 if successful, EOF if no replies were left in this 'msg',
+ * otherwise a positive errno value. */
+int
+ofputil_decode_group_desc_reply(struct ofputil_group_desc *gd,
+ struct ofpbuf *msg, enum ofp_version version)
+{
+ struct ofp11_group_desc_stats *ogds;
+ size_t length;
+
+ if (!msg->frame) {
+ ofpraw_pull_assert(msg);
+ }
+
+ if (!ofpbuf_size(msg)) {
+ return EOF;
+ }
+
+ ogds = ofpbuf_try_pull(msg, sizeof *ogds);
+ if (!ogds) {
+ VLOG_WARN_RL(&bad_ofmsg_rl, "OFPST11_GROUP_DESC reply has %"PRIu32" "
+ "leftover bytes at end", ofpbuf_size(msg));
+ return OFPERR_OFPBRC_BAD_LEN;
+ }
+ gd->type = ogds->type;
+ gd->group_id = ntohl(ogds->group_id);
+
+ length = ntohs(ogds->length);
+ if (length < sizeof *ogds || length - sizeof *ogds > ofpbuf_size(msg)) {
+ VLOG_WARN_RL(&bad_ofmsg_rl, "OFPST11_GROUP_DESC reply claims invalid "
+ "length %"PRIuSIZE, length);
+ return OFPERR_OFPBRC_BAD_LEN;
+ }
+
+ return ofputil_pull_buckets(msg, length - sizeof *ogds, version,
+ &gd->buckets);
+}
+
+/* Converts abstract group mod 'gm' into a message for OpenFlow version
+ * 'ofp_version' and returns the message. */
+struct ofpbuf *
+ofputil_encode_group_mod(enum ofp_version ofp_version,
+ const struct ofputil_group_mod *gm)
+{
+ struct ofpbuf *b;
+ struct ofp11_group_mod *ogm;
+ size_t start_ogm;
+ size_t start_bucket;
+ struct ofputil_bucket *bucket;
+ struct ofp11_bucket *ob;
+
+ switch (ofp_version) {
+ case OFP10_VERSION: {
+ if (gm->command == OFPGC11_ADD) {
+ ovs_fatal(0, "add-group needs OpenFlow 1.1 or later "
+ "(\'-O OpenFlow11\')");
+ } else if (gm->command == OFPGC11_MODIFY) {
+ ovs_fatal(0, "mod-group needs OpenFlow 1.1 or later "
+ "(\'-O OpenFlow11\')");
+ } else {
+ ovs_fatal(0, "del-groups needs OpenFlow 1.1 or later "
+ "(\'-O OpenFlow11\')");
+ }
+ }
+
+ case OFP11_VERSION:
+ case OFP12_VERSION:
+ case OFP13_VERSION:
+ case OFP14_VERSION:
+ b = ofpraw_alloc(OFPRAW_OFPT11_GROUP_MOD, ofp_version, 0);
+ start_ogm = ofpbuf_size(b);
+ ofpbuf_put_zeros(b, sizeof *ogm);
+
+ LIST_FOR_EACH (bucket, list_node, &gm->buckets) {
+ start_bucket = ofpbuf_size(b);
+ ofpbuf_put_zeros(b, sizeof *ob);
+ if (bucket->ofpacts && bucket->ofpacts_len) {
+ ofpacts_put_openflow_actions(bucket->ofpacts,
+ bucket->ofpacts_len, b,
+ ofp_version);
+ }
+ ob = ofpbuf_at_assert(b, start_bucket, sizeof *ob);
+ ob->len = htons(ofpbuf_size(b) - start_bucket);;
+ ob->weight = htons(bucket->weight);
+ ob->watch_port = ofputil_port_to_ofp11(bucket->watch_port);
+ ob->watch_group = htonl(bucket->watch_group);
+ }
+ ogm = ofpbuf_at_assert(b, start_ogm, sizeof *ogm);
+ ogm->command = htons(gm->command);
+ ogm->type = gm->type;
+ ogm->group_id = htonl(gm->group_id);
+
+ break;
+