diff mbox

[ovs-dev,2/6] OF1.5/EXT-334 OXS/Individal Flow Statistics -2

Message ID 1495039998-24847-1-git-send-email-harivelam.lavanya@tcs.com
State Changes Requested
Headers show

Commit Message

SatyaValli May 17, 2017, 4:53 p.m. UTC
From: Harivelam Lavanya <harivelam.lavanya@tcs.com>


Changes in ofproto.at and ofproto-dpif.at :-  

For 1.5 version "flags"  variable has been removed from flow_stats_reply structure.
For avoiding the make check failures for OF1.5 version add-flow with flags,
below test cases has been modified for 1.5 version in respective .at files

Files           testcase  
ofproto.at      0884
ofproto-dpif    1126

In ofproto.at there is one test case numbered 0884 and 1126 in ofproto-dpif.at 
which is validating flagsfor OF1.5 this testcase is not valid, because
in stats reply flags are not explicitly communicated,I've removed this test case.
Before removing this test case is failing for 1.5 version.

Signed-off-by:  Lavanya Harivelam <harivelam.lavanya@tcs.com>
Co-authored-by: Satya Valli <satyavalli.rama@tcs.com>

---
 lib/ofp-util.c        | 125 ++++++++++++++++-
 lib/ox-stat.c         | 365 +++++++++++++++++++++++++++++++++++++++++++++++++-
 lib/ox-stat.h         |   3 +-
 tests/ofproto-dpif.at |   1 -
 tests/ofproto.at      |   4 -
 5 files changed, 487 insertions(+), 11 deletions(-)
diff mbox

Patch

diff --git a/lib/ofp-util.c b/lib/ofp-util.c
index bdf89b6..4813d77 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -50,9 +50,12 @@ 
 #include "unaligned.h"
 #include "util.h"
 #include "uuid.h"
+#include "ox-stat.h"
 
 VLOG_DEFINE_THIS_MODULE(ofp_util);
 
+extern uint8_t oxs_field_set;
+
 /* Rate limit for OpenFlow message parse errors.  These always indicate a bug
  * in the peer and so there's not much point in showing a lot of them. */
 static struct vlog_rate_limit bad_ofmsg_rl = VLOG_RATE_LIMIT_INIT(1, 5);
@@ -2314,6 +2317,40 @@  ofputil_decode_ofpst11_flow_request(struct ofputil_flow_stats_request *fsr,
 }
 
 static enum ofperr
+ofputil_decode_ofpst15_flow_request(struct ofputil_flow_stats_request *fsr,
+                                    struct ofpbuf *b, bool aggregate,
+                                    const struct tun_table *tun_table,
+                                    const struct vl_mff_map *vl_mff_map)
+{
+    const struct ofp15_oxs_flow_stats_request *ofsr;
+    enum ofperr error, stat_error;
+    uint16_t statlen;
+
+    ofsr = ofpbuf_pull(b, sizeof *ofsr);
+    fsr->aggregate = aggregate;
+    fsr->table_id = ofsr->table_id;
+
+    error = ofputil_port_from_ofp11(ofsr->out_port, &fsr->out_port);
+    if (error) {
+        return error;
+    }
+
+    fsr->out_group = ntohl(ofsr->out_group);
+    fsr->cookie = ofsr->cookie;
+    fsr->cookie_mask = ofsr->cookie_mask;
+
+    error = ofputil_pull_ofp11_match(b, tun_table, vl_mff_map, &fsr->match,
+                                     NULL);
+    stat_error = oxs_pull_stat(b, NULL, &statlen);
+
+    if (error || stat_error) {
+        return error;
+    }
+
+    return 0;
+}
+
+static enum ofperr
 ofputil_decode_nxst_flow_request(struct ofputil_flow_stats_request *fsr,
                                  struct ofpbuf *b, bool aggregate,
                                  const struct tun_table *tun_table,
@@ -2763,6 +2800,11 @@  ofputil_decode_flow_stats_request(struct ofputil_flow_stats_request *fsr,
         return ofputil_decode_ofpst11_flow_request(fsr, &b, true, tun_table,
                                                    vl_mff_map);
 
+    case OFPRAW_OFPST15_OXS_FLOW_REQUEST:
+        oxs_field_set = 0;
+        return ofputil_decode_ofpst15_flow_request(fsr, &b, false, tun_table,
+                                                   vl_mff_map);
+
     case OFPRAW_NXST_FLOW_REQUEST:
         return ofputil_decode_nxst_flow_request(fsr, &b, false, tun_table,
                                                 vl_mff_map);
@@ -2788,12 +2830,29 @@  ofputil_encode_flow_stats_request(const struct ofputil_flow_stats_request *fsr,
     enum ofpraw raw;
 
     switch (protocol) {
+    case OFPUTIL_P_OF15_OXM:
+    case OFPUTIL_P_OF16_OXM: {
+        struct ofp15_oxs_flow_stats_request *ofsr;
+
+        raw = (fsr->aggregate
+               ? OFPRAW_OFPST11_AGGREGATE_REQUEST
+               : OFPRAW_OFPST15_OXS_FLOW_REQUEST);
+        msg = ofpraw_alloc(raw, ofputil_protocol_to_ofp_version(protocol),
+                           ofputil_match_typical_len(protocol));
+        ofsr = ofpbuf_put_zeros(msg, sizeof *ofsr);
+        ofsr->table_id = fsr->table_id;
+        ofsr->out_port = ofputil_port_to_ofp11(fsr->out_port);
+        ofsr->out_group = htonl(fsr->out_group);
+        ofsr->cookie = fsr->cookie;
+        ofsr->cookie_mask = fsr->cookie_mask;
+        ofputil_put_ofp11_match(msg, &fsr->match, protocol);
+        oxs_put_stat(msg, NULL, ofputil_protocol_to_ofp_version(protocol));
+        break;
+    }
     case OFPUTIL_P_OF11_STD:
     case OFPUTIL_P_OF12_OXM:
     case OFPUTIL_P_OF13_OXM:
-    case OFPUTIL_P_OF14_OXM:
-    case OFPUTIL_P_OF15_OXM:
-    case OFPUTIL_P_OF16_OXM: {
+    case OFPUTIL_P_OF14_OXM: {
         struct ofp11_flow_stats_request *ofsr;
 
         raw = (fsr->aggregate
@@ -2893,6 +2952,46 @@  ofputil_decode_flow_stats_reply(struct ofputil_flow_stats *fs,
 
     if (!msg->size) {
         return EOF;
+    } else if (raw == OFPRAW_OFPST15_OXS_FLOW_REPLY) {
+        const struct ofp15_oxs_flow_stats_reply *ofs;
+        size_t length;
+        uint16_t padded_match_len;
+        uint16_t stat_len;
+
+        ofs = ofpbuf_try_pull(msg, sizeof *ofs);
+        if (!ofs) {
+            VLOG_WARN_RL(&bad_ofmsg_rl, "OFPST_FLOW reply has %" PRIu32
+                         " leftover " "bytes at end", msg->size);
+            return EINVAL;
+        }
+
+        length = ntohs(ofs->length);
+        if (length < sizeof *ofs) {
+            VLOG_WARN_RL(&bad_ofmsg_rl, "OFPST_FLOW reply claims invalid "
+                         "length %" PRIuSIZE, length);
+            return EINVAL;
+        }
+
+        if (ofputil_pull_ofp11_match
+            (msg, NULL, NULL, &fs->match, &padded_match_len)) {
+            VLOG_WARN_RL(&bad_ofmsg_rl, "OFPST_FLOW reply bad match");
+            return EINVAL;
+        }
+
+        fs->priority = ntohs(ofs->priority);
+        fs->table_id = ofs->table_id;
+        fs->duration_sec = 0;
+        fs->duration_nsec = 0;
+        fs->idle_age = 0;
+        fs->packet_count = 0;
+        fs->byte_count = 0;
+        fs->cookie = 0;
+
+        if (oxs_pull_stat(msg, fs, &stat_len)) {
+            VLOG_WARN_RL(&bad_ofmsg_rl, "OXS OFPST_FLOW reply bad match");
+            return EINVAL;
+        }
+        instructions_len = length - sizeof *ofs - padded_match_len - stat_len;
     } else if (raw == OFPRAW_OFPST11_FLOW_REPLY
                || raw == OFPRAW_OFPST13_FLOW_REPLY) {
         const struct ofp11_flow_stats *ofs;
@@ -3067,7 +3166,23 @@  ofputil_append_flow_stats_reply(const struct ofputil_flow_stats *fs,
     orig_tun_table = fs->match.flow.tunnel.metadata.tab;
     fs_->match.flow.tunnel.metadata.tab = tun_table;
 
-    if (raw == OFPRAW_OFPST11_FLOW_REPLY || raw == OFPRAW_OFPST13_FLOW_REPLY) {
+    if (raw == OFPRAW_OFPST15_OXS_FLOW_REPLY) {
+        struct ofp15_oxs_flow_stats_reply *ofs;
+
+        ofpbuf_put_uninit(reply, sizeof *ofs);
+        oxm_put_match(reply, &fs->match, version);
+        oxs_put_stat(reply, fs, version);
+        ofpacts_put_openflow_instructions(fs->ofpacts, fs->ofpacts_len, reply,
+                                      version);
+
+        ofs = ofpbuf_at_assert(reply, start_ofs, sizeof *ofs);
+        ofs->length = htons(reply->size - start_ofs);
+        ofs->table_id = fs->table_id;
+        ofs->priority = htons(fs->priority);
+        ofs->reason = 0;
+        memset(ofs->pad2, 0, sizeof ofs->pad2);
+    } else if (raw == OFPRAW_OFPST11_FLOW_REPLY ||
+               raw == OFPRAW_OFPST13_FLOW_REPLY) {
         struct ofp11_flow_stats *ofs;
 
         ofpbuf_put_uninit(reply, sizeof *ofs);
@@ -10150,6 +10265,7 @@  ofputil_is_bundlable(enum ofptype type)
     case OFPTYPE_GET_ASYNC_REQUEST:
     case OFPTYPE_DESC_STATS_REQUEST:
     case OFPTYPE_FLOW_STATS_REQUEST:
+    case OFPTYPE_OXS_FLOW_STATS_REQUEST:
     case OFPTYPE_AGGREGATE_STATS_REQUEST:
     case OFPTYPE_TABLE_STATS_REQUEST:
     case OFPTYPE_TABLE_FEATURES_STATS_REQUEST:
@@ -10178,6 +10294,7 @@  ofputil_is_bundlable(enum ofptype type)
     case OFPTYPE_QUEUE_GET_CONFIG_REPLY:
     case OFPTYPE_DESC_STATS_REPLY:
     case OFPTYPE_FLOW_STATS_REPLY:
+    case OFPTYPE_OXS_FLOW_STATS_REPLY:
     case OFPTYPE_QUEUE_STATS_REPLY:
     case OFPTYPE_PORT_STATS_REPLY:
     case OFPTYPE_TABLE_STATS_REPLY:
diff --git a/lib/ox-stat.c b/lib/ox-stat.c
index 96f019c..8bea083 100644
--- a/lib/ox-stat.c
+++ b/lib/ox-stat.c
@@ -16,7 +16,6 @@ 
 
 #include <config.h>
 #include "nx-match.h"
-#include "ox-stat.h"
 #include <netinet/icmp6.h>
 #include "classifier.h"
 #include "colors.h"
@@ -34,6 +33,7 @@ 
 #include "tun-metadata.h"
 #include "unaligned.h"
 #include "util.h"
+#include "ox-stat.h"
 
 VLOG_DEFINE_THIS_MODULE(ox_stat);
 
@@ -129,6 +129,8 @@  uint8_t oxs_field_set;
 static const struct oxs_field *oxs_field_by_header(uint32_t header);
 static const struct oxs_field *oxs_field_by_id(enum oxs_ofb_stat_fields,
                                                enum ofp_version);
+void oxs_put__(struct ofpbuf *, enum oxs_ofb_stat_fields,
+               enum ofp_version, const void *, const void *, size_t);
 static bool is_experimenter_oxs(uint64_t header);
 static int oxs_experimenter_len(uint64_t header);
 static int oxs_payload_len (uint64_t header);
@@ -137,7 +139,20 @@  static uint64_t oxs_header_get(enum oxs_ofb_stat_fields id,
                                 enum ofp_version version);
 static int oxs_pull_header__(struct ofpbuf *b, uint64_t * header,
                               const struct oxs_field **field);
+static enum ofperr oxs_pull_entry__(struct ofpbuf *b, uint64_t * header,
+                                    const struct oxs_field **field_,
+                                    struct ofputil_flow_stats *fs);
+static enum ofperr oxs_pull_match_entry(struct ofpbuf *b,
+                                        const struct oxs_field **field,
+                                        struct ofputil_flow_stats *fs);
+static enum ofperr oxs_pull_raw(const uint8_t *, unsigned int ,
+                                struct ofputil_flow_stats *fs,
+                                ovs_be64 * cookie, ovs_be64 * cookie_mask);
 static void oxs_init(void);
+static void oxs_put_header__(struct ofpbuf *b, uint64_t header);
+static void oxs_put_header_len(struct ofpbuf *b,
+                               enum oxs_ofb_stat_fields field,
+                               enum ofp_version version);
 
 static bool
 is_experimenter_oxs(uint64_t header)
@@ -213,6 +228,181 @@  error:
     return OFPERR_OFPBMC_BAD_LEN;
 }
 
+static enum ofperr
+oxs_pull_entry__(struct ofpbuf *b, uint64_t * header,
+                 const struct oxs_field **field_,
+                 struct ofputil_flow_stats *fs)
+{
+    const struct oxs_field *field;
+    enum ofperr header_error;
+    unsigned int payload_len;
+    const uint8_t *payload;
+
+    header_error = oxs_pull_header__(b, header, &field);
+
+    if (header_error && header_error != OFPERR_OFPBMC_BAD_FIELD) {
+        return header_error;
+    }
+
+    payload_len = oxs_payload_len(*header);
+    payload = ofpbuf_try_pull(b, payload_len);
+    if (!payload) {
+        return OFPERR_OFPBMC_BAD_LEN;
+    }
+
+    if (fs && field) {
+        switch (field->id) {
+        case OFPXST_OFB_DURATION:
+            {
+                uint64_t duration = 0;
+
+                memcpy(&duration, payload, sizeof (duration));
+                duration = ntohll(duration);
+                fs->duration_sec = ((uint32_t) ((duration &
+                                                 0xFFFFFFFF00000000) >> 32));
+                fs->duration_nsec = ((uint32_t) (duration & 0xFFFFFFFF));
+            }
+            break;
+        case OFPXST_OFB_IDLE_TIME:
+            {
+                uint64_t idle_time = 0;
+
+                memcpy(&idle_time, payload, sizeof (idle_time));
+                idle_time = ntohll(idle_time);
+                fs->idle_age = ((idle_time & 0xFFFFFFFF00000000) >> 32);
+            }
+            break;
+        case OFPXST_OFB_PACKET_COUNT:
+            {
+                uint64_t packet_count;
+
+                memcpy(&packet_count, payload, sizeof (packet_count));
+                fs->packet_count = ntohll(packet_count);
+            }
+            break;
+        case OFPXST_OFB_BYTE_COUNT:
+            {
+                uint64_t byte_count;
+
+                memcpy(&byte_count, payload, sizeof (byte_count));
+                fs->byte_count = ntohll(byte_count);
+            }
+            break;
+        case OFPXST_OFB_FLOW_COUNT:
+            break;
+        }
+    }
+
+    if (field_) {
+        *field_ = field;
+        return header_error;
+    }
+
+    return 0;
+}
+
+static enum ofperr
+oxs_pull_match_entry(struct ofpbuf *b,
+                     const struct oxs_field **field,
+                     struct ofputil_flow_stats *fs)
+{
+    enum ofperr error;
+    uint64_t header;
+
+    error = oxs_pull_entry__(b, &header, field, fs);
+    if (error) {
+        return error;
+    }
+    return 0;
+}
+
+static enum ofperr
+oxs_pull_raw(const uint8_t * p, unsigned int stat_len,
+             struct ofputil_flow_stats *fs,
+             ovs_be64 * cookie, ovs_be64 * cookie_mask)
+{
+    ovs_assert((cookie != NULL) == (cookie_mask != NULL));
+    if (cookie) {
+        *cookie = *cookie_mask = htonll(0);
+    }
+
+    struct ofpbuf b = ofpbuf_const_initializer(p, stat_len);
+
+    while (b.size) {
+        const uint8_t *pos = b.data;
+        const struct oxs_field *field;
+        union mf_value value;
+        union mf_value mask;
+        enum ofperr error;
+
+        error = oxs_pull_match_entry(&b, &field, fs);
+        if (error) {
+            if (error == OFPERR_OFPBMC_BAD_FIELD && !false) {
+                continue;
+            }
+        } else if (!field) {
+            if (!cookie) {
+                error = OFPERR_OFPBMC_BAD_FIELD;
+            } else if (*cookie_mask) {
+                error = OFPERR_OFPBMC_DUP_FIELD;
+            } else {
+                *cookie = value.be64;
+                *cookie_mask = mask.be64;
+            }
+        } else {
+            if (field->id == OFPXST_OFB_DURATION) {
+                oxs_field_set |= 1 << 0;
+            } else if (field->id == OFPXST_OFB_IDLE_TIME) {
+                oxs_field_set |= 1 << 1;
+            } else if (field->id == OFPXST_OFB_FLOW_COUNT) {
+                oxs_field_set |= 1 << 2;
+            } else if (field->id == OFPXST_OFB_PACKET_COUNT) {
+                oxs_field_set |= 1 << 3;
+            } else if (field->id == OFPXST_OFB_BYTE_COUNT) {
+                oxs_field_set |= 1 << 4;
+            }
+        }
+        if (error) {
+            VLOG_DBG_RL(&rl, "error parsing OXS at offset %" PRIdPTR " "
+                        "within match (%s)", pos - p, ofperr_to_string(error));
+            return error;
+        }
+    }
+    return 0;
+}
+
+/* Retrieve  struct ofp_oxs_stat from 'b', followed by the flow entry
+ * statistics in OXS format.
+ *
+ * Returns error if message parsing fails, otherwise returns zero . */
+int
+oxs_pull_stat(struct ofpbuf *b, struct ofputil_flow_stats *fs,
+              uint16_t * statlen)
+{
+    struct ofp_oxs_stat *oxs = b->data;
+    uint8_t *p;
+    uint16_t stat_len;
+
+    stat_len = ntohs(oxs->length);
+    if (stat_len < sizeof *oxs) {
+        return OFPERR_OFPBMC_BAD_LEN;
+    }
+
+    p = ofpbuf_try_pull(b, ROUND_UP(stat_len, 8));
+    if (!p) {
+        VLOG_DBG_RL(&rl, "oxs length %u, rounded up to a "
+                    "multiple of 8, is longer than space in message (max "
+                    "length %" PRIu32 ")", stat_len, b->size);
+        return OFPERR_OFPBMC_BAD_LEN;
+    }
+    *statlen = ROUND_UP(stat_len, 8);
+    return oxs_pull_raw(p + sizeof *oxs, stat_len - sizeof *oxs, fs, NULL,
+                        NULL);
+}
+
+static struct hmap oxs_header_map;
+static struct hmap oxs_name_map;
+
 static void
 oxs_init(void)
 {
@@ -236,3 +426,176 @@  oxs_init(void)
     }
 }
 
+static const struct oxs_field *
+oxs_field_by_header(uint32_t header)
+{
+    const struct oxs_field_index *oxfs;
+    uint32_t header_no_len;
+
+    oxs_init();
+
+    header_no_len = oxs_header_no_len(header);
+    HMAP_FOR_EACH_IN_BUCKET(oxfs, header_node, hash_int(header_no_len, 0),
+                            &oxs_header_map) {
+        if (header_no_len == oxs_header_no_len(oxfs->fs.header)) {
+            if (OXS_LENGTH(header) == OXS_LENGTH(oxfs->fs.header)) {
+                return &oxfs->fs;
+            } else {
+                return NULL;
+            }
+        }
+    }
+    return NULL;
+}
+
+static const struct oxs_field *
+oxs_field_by_id(enum oxs_ofb_stat_fields id, enum ofp_version version)
+{
+    const struct oxs_field_index *oxfs;
+    const struct oxs_field *fs = NULL;
+
+    oxs_init();
+
+    LIST_FOR_EACH(oxfs, ox_node, &oxs_ox_map[id]) {
+        if (!fs || version >= oxfs->fs.version) {
+            fs = &oxfs->fs;
+        }
+    }
+    return fs;
+}
+
+static void
+oxs_put_header__(struct ofpbuf *b, uint64_t header)
+{
+    ovs_be32 network_header = htonl(header);
+
+    ofpbuf_put(b, &network_header, oxs_header_len(header));
+}
+
+static void
+oxs_put_header_len(struct ofpbuf *b, enum oxs_ofb_stat_fields field,
+                   enum ofp_version version)
+{
+    uint32_t header = oxs_header_get(field, version);
+
+    header = OXS_HEADER(OXS_CLASS(header),
+                        OXS_FIELD(header), OXS_LENGTH(header));
+    oxs_put_header__(b, header);
+}
+
+void
+oxs_put__(struct ofpbuf *b, enum oxs_ofb_stat_fields field,
+          enum ofp_version version,
+          const void *value, const void *mask, size_t n_bytes)
+{
+    oxs_put_header_len(b, field, version);
+    ofpbuf_put(b, value, n_bytes);
+    if (mask) {
+        ofpbuf_put(b, mask, n_bytes);
+    }
+
+}
+
+/* Appends to 'b' the flow entry statistics in a flexible encoding ,OXS
+ * format.
+ *
+ * OXS is a TLV format to express flow entry statistics.  Specify the OpenFlow
+ * version in use as 'version'.
+ *
+ * Returns the number of bytes appended to 'b'.
+ *
+ * This function can cause 'b''s data to be reallocated.
+ *
+ * Returns zero or the number of bytes appended to 'b'. */
+static int
+ox_put_raw(struct ofpbuf *b, enum ofp_version oxs,
+           const struct ofputil_flow_stats *fs,
+           ovs_be64 cookie, ovs_be64 cookie_mask)
+{
+    const size_t start_len = b->size;
+    int stat_len;
+
+    if (oxs_field_set & 1 << 0) {
+        uint64_t duration = 0;
+
+        if (fs) {
+            duration = (uint64_t) fs->duration_sec << 32 | fs->duration_nsec;
+            duration = htonll(duration);
+        }
+        oxs_put__(b, OFPXST_OFB_DURATION, oxs, &duration, NULL,
+                  OXS_STATS_DURATION_LEN);
+    }
+    if (oxs_field_set & 1 << 1) {
+        uint64_t idl_time = 0;
+
+        if (fs) {
+            idl_time = (uint64_t) fs->idle_age << 32;
+            idl_time = htonll(idl_time);
+        }
+        oxs_put__(b, OFPXST_OFB_IDLE_TIME, oxs, &idl_time, NULL,
+                  OXS_STATS_IDLE_TIME_LEN);
+    }
+    if (oxs_field_set & 1 << 2) {
+        uint32_t flow_count = 0;
+
+        oxs_put__(b, OFPXST_OFB_FLOW_COUNT, oxs, &flow_count, NULL,
+                  OXS_STATS_FLOW_COUNT_LEN);
+    }
+    if (oxs_field_set & 1 << 3) {
+        uint64_t pkt_count = 0;
+
+        if (fs) {
+            pkt_count = fs->packet_count;
+            pkt_count = htonll(pkt_count);
+        }
+        oxs_put__(b, OFPXST_OFB_PACKET_COUNT, oxs, &pkt_count, NULL,
+                  OXS_STATS_PACKET_COUNT_LEN);
+    }
+    if (oxs_field_set & 1 << 4) {
+        uint64_t byte_count = 0;
+
+        if (fs) {
+            byte_count = fs->byte_count;
+            byte_count = htonll(byte_count);
+        }
+        oxs_put__(b, OFPXST_OFB_BYTE_COUNT, oxs, &byte_count, NULL,
+                  OXS_STATS_BYTE_COUNT_LEN);
+    }
+    if (cookie_mask) {
+        cookie &= cookie_mask;
+        oxs_put_header__(b, OXS_OX_COOKIE);
+        ofpbuf_put(b, &cookie, sizeof cookie);
+    }
+    stat_len = b->size - start_len;
+    return stat_len;
+}
+
+/* Appends to 'b' an struct ofp_oxs_stat followed by the flow entry statistics
+ * in OXS format , plus enough zero bytes to pad the data appended out to a
+ * multiple of 8.
+ *
+ * Specify the OpenFlow version in use as 'version'.
+ *
+ * This function can cause 'b''s data to be reallocated.
+ *
+ * Returns the number of bytes appended to 'b', excluding the padding.Never
+ * returns zero. */
+int
+oxs_put_stat(struct ofpbuf *b, const struct ofputil_flow_stats *fs,
+             enum ofp_version version)
+{
+    int stat_len;
+    struct ofp_oxs_stat *oxs;
+    size_t start_len = b->size;
+    ovs_be64 cookie = htonll(0), cookie_mask = htonll(0);
+
+    ofpbuf_put_uninit(b, sizeof *oxs);
+    stat_len = (ox_put_raw(b, version, fs, cookie, cookie_mask)
+                + sizeof *oxs);
+    ofpbuf_put_zeros(b, PAD_SIZE(stat_len, 8));
+    oxs = ofpbuf_at(b, start_len, sizeof *oxs);
+    oxs->reserved = htons(0);
+    oxs->length = htons(stat_len);
+    return stat_len;
+}
+
diff --git a/lib/ox-stat.h b/lib/ox-stat.h
index 1dd8ab5..33c30ec 100644
--- a/lib/ox-stat.h
+++ b/lib/ox-stat.h
@@ -28,5 +28,6 @@ 
 
 int oxs_put_stat(struct ofpbuf *, const struct ofputil_flow_stats *,
                  enum ofp_version );
-
+int oxs_pull_stat(struct ofpbuf *,struct ofputil_flow_stats *,
+                  uint16_t *);
 #endif /* ox_stat.h */
diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index 1f6cd84..b97e73e 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -7004,7 +7004,6 @@  flow_mods_reset_counts () {
 # OpenFlow versions >= 1.3 should behave the same way
 flow_mods_reset_counts 13
 flow_mods_reset_counts 14
-flow_mods_reset_counts 15
 
 OVS_VSWITCHD_STOP
 AT_CLEANUP
diff --git a/tests/ofproto.at b/tests/ofproto.at
index 5431f4e..5eedab1 100644
--- a/tests/ofproto.at
+++ b/tests/ofproto.at
@@ -1392,10 +1392,6 @@  AT_CHECK([ovs-ofctl -O OpenFlow14 dump-flows br0 | ofctl_strip], [0], [dnl
 OFPST_FLOW reply (OF1.4):
  check_overlap reset_counts in_port=1 actions=drop
 ])
-AT_CHECK([ovs-ofctl -O OpenFlow15 dump-flows br0 | ofctl_strip], [0], [dnl
-OFPST_FLOW reply (OF1.5):
- check_overlap reset_counts in_port=1 actions=drop
-])
 OVS_VSWITCHD_STOP
 AT_CLEANUP