Merge branch 'master' of github.com:floodlight/oftest
diff --git a/tests/flow_query.py b/tests/flow_query.py
new file mode 100644
index 0000000..0538365
--- /dev/null
+++ b/tests/flow_query.py
@@ -0,0 +1,2703 @@
+"""
+Flow query test case.
+
+Attempts to fill switch to capacity with randomized flows, and ensure that
+they all are read back correctly.
+"""
+
+# COMMON TEST PARAMETERS
+#
+# Name: wildcards
+# Type: number
+# Description:
+# Overrides bitmap of supported wildcards reported by switch
+# Default: none
+#
+# Name: actions
+# Type: number
+# Description:
+# Overrides bitmap of supported actions reported by switch
+# Default: none
+#
+# Name: conservative_ordered_actions
+# Type: boolean (True or False)
+# Description:
+# Compare flow actions lists as unordered
+# Default: True
+
+
+import math
+
+import logging
+
+import unittest
+import random
+
+import oftest.controller  as controller
+import oftest.cstruct     as ofp
+import oftest.message     as message
+import oftest.dataplane   as dataplane
+import oftest.action      as action
+import oftest.action_list as action_list
+import oftest.parse       as parse
+import pktact
+import basic
+
+from testutils import *
+from time import sleep
+
+#@var port_map Local copy of the configuration map from OF port
+# numbers to OS interfaces
+fq_port_map = None
+#@var fq_logger Local logger object
+fq_logger = None
+#@var fq_config Local copy of global configuration data
+fq_config = None
+
+# For test priority
+test_prio = {}
+
+
+def test_set_init(config):
+    """
+    Set up function for packet action test classes
+
+    @param config The configuration dictionary; see oft
+    """
+
+    basic.test_set_init(config)
+
+    global fq_port_map
+    global fq_logger
+    global fq_config
+
+    fq_logger = logging.getLogger("flowq")
+    fq_logger.info("Initializing test set")
+    fq_port_map = config["port_map"]
+    fq_config = config
+
+
+def flip_coin():
+    return random.randint(1, 100) <= 50
+
+
+def shuffle(list):
+    n = len(list)
+    lim = n * n
+    i = 0
+    while i < lim:
+        a = random.randint(0, n - 1)
+        b = random.randint(0, n - 1)
+        temp = list[a]
+        list[a] = list[b]
+        list[b] = temp
+        i = i + 1
+    return list
+
+
+def rand_pick(list):
+    return list[random.randint(0, len(list) - 1)]
+
+def rand_dl_addr():
+    return [random.randint(0, 255) & ~1,
+            random.randint(0, 255),
+            random.randint(0, 255),
+            random.randint(0, 255),
+            random.randint(0, 255),
+            random.randint(0, 255)
+            ]
+
+def rand_nw_addr():
+    return random.randint(0, (1 << 32) - 1)
+
+
+class Flow_Info:
+    # Members:
+    # priorities - list of flow priorities
+    # dl_addrs   - list of MAC addresses
+    # vlans      - list of VLAN ids
+    # ethertypes - list of Ethertypes
+    # ip_addrs   - list of IP addresses
+    # ip_tos     - list of IP TOS values
+    # ip_protos  - list of IP protocols
+    # l4_ports   - list of L4 ports
+
+    def __init__(self):
+        priorities = []
+        dl_addrs   = []
+        vlans      = []
+        ethertypes = []
+        ip_addrs   = []
+        ip_tos     = []
+        ip_protos  = []
+        l4_ports   = []
+
+    def rand(self, n):
+        self.priorities = []
+        i = 0
+        while i < n:
+            self.priorities.append(random.randint(1, 65534))
+            i = i + 1
+    
+        self.dl_addrs = []
+        i = 0
+        while i < n:
+            self.dl_addrs.append(rand_dl_addr())
+            i = i + 1
+    
+        self.vlans = []
+        i = 0
+        while i < n:
+            self.vlans.append(random.randint(1, 4094))
+            i = i + 1
+    
+        self.ethertypes = [0x0800, 0x0806]
+        i = 0
+        while i < n:
+            self.ethertypes.append(random.randint(0, (1 << 16) - 1))
+            i = i + 1
+        self.ethertypes = shuffle(self.ethertypes)[0 : n]
+    
+        self.ip_addrs = []
+        i = 0
+        while i < n:
+            self.ip_addrs.append(rand_nw_addr())
+            i = i + 1
+    
+        self.ip_tos = []
+        i = 0
+        while i < n:
+            self.ip_tos.append(random.randint(0, (1 << 8) - 1) & ~3)
+            i = i + 1
+    
+        self.ip_protos = [1, 6, 17]
+        i = 0
+        while i < n:
+            self.ip_protos.append(random.randint(0, (1 << 8) - 1))
+            i = i + 1
+        self.ip_protos = shuffle(self.ip_protos)[0 : n]
+    
+        self.l4_ports = []
+        i = 0
+        while i < n:
+            self.l4_ports.append(random.randint(0, (1 << 16) - 1))
+            i = i + 1
+
+    def rand_priority(self):
+        return rand_pick(self.priorities)
+
+    def rand_dl_addr(self):
+        return rand_pick(self.dl_addrs)
+
+    def rand_vlan(self):
+        return rand_pick(self.vlans)
+
+    def rand_ethertype(self):
+        return rand_pick(self.ethertypes)
+
+    def rand_ip_addr(self):
+        return rand_pick(self.ip_addrs)
+
+    def rand_ip_tos(self):
+        return rand_pick(self.ip_tos)
+
+    def rand_ip_proto(self):
+        return rand_pick(self.ip_protos)
+
+    def rand_l4_port(self):
+        return rand_pick(self.l4_ports)
+
+
+# TBD - These don't belong here
+
+all_wildcards_list = [ofp.OFPFW_IN_PORT,
+                      ofp.OFPFW_DL_DST,
+                      ofp.OFPFW_DL_SRC,
+                      ofp.OFPFW_DL_VLAN,
+                      ofp.OFPFW_DL_VLAN_PCP,
+                      ofp.OFPFW_DL_TYPE,
+                      ofp.OFPFW_NW_TOS,
+                      ofp.OFPFW_NW_PROTO,
+                      ofp.OFPFW_NW_SRC_MASK,
+                      ofp.OFPFW_NW_DST_MASK,
+                      ofp.OFPFW_TP_SRC,
+                      ofp.OFPFW_TP_DST
+                      ]
+
+# TBD - Need this because there are duplicates in ofp.ofp_flow_wildcards_map
+# -- FIX
+all_wildcard_names = {
+    1                               : 'OFPFW_IN_PORT',
+    2                               : 'OFPFW_DL_VLAN',
+    4                               : 'OFPFW_DL_SRC',
+    8                               : 'OFPFW_DL_DST',
+    16                              : 'OFPFW_DL_TYPE',
+    32                              : 'OFPFW_NW_PROTO',
+    64                              : 'OFPFW_TP_SRC',
+    128                             : 'OFPFW_TP_DST',
+    1048576                         : 'OFPFW_DL_VLAN_PCP',
+    2097152                         : 'OFPFW_NW_TOS'
+}
+
+
+def wildcard_set(x, w, val):
+    result = x
+    if w == ofp.OFPFW_NW_SRC_MASK:
+        result = (result & ~ofp.OFPFW_NW_SRC_MASK) \
+                 | (val << ofp.OFPFW_NW_SRC_SHIFT)
+    elif w == ofp.OFPFW_NW_DST_MASK:
+        result = (result & ~ofp.OFPFW_NW_DST_MASK) \
+                 | (val << ofp.OFPFW_NW_DST_SHIFT)
+    elif val == 0:
+        result = result & ~w
+    else:
+        result = result | w
+    return result
+
+def wildcard_get(x, w):
+    if w == ofp.OFPFW_NW_SRC_MASK:
+        return (x & ofp.OFPFW_NW_SRC_MASK) >> ofp.OFPFW_NW_SRC_SHIFT
+    if w == ofp.OFPFW_NW_DST_MASK:
+        return (x & ofp.OFPFW_NW_DST_MASK) >> ofp.OFPFW_NW_DST_SHIFT
+    return 1 if (x & w) != 0 else 0
+
+
+all_actions_list = [ofp.OFPAT_OUTPUT,
+                    ofp.OFPAT_SET_VLAN_VID,
+                    ofp.OFPAT_SET_VLAN_PCP,
+                    ofp.OFPAT_STRIP_VLAN,
+                    ofp.OFPAT_SET_DL_SRC,
+                    ofp.OFPAT_SET_DL_DST,
+                    ofp.OFPAT_SET_NW_SRC,
+                    ofp.OFPAT_SET_NW_DST,
+                    ofp.OFPAT_SET_NW_TOS,
+                    ofp.OFPAT_SET_TP_SRC,
+                    ofp.OFPAT_SET_TP_DST,
+                    ofp.OFPAT_ENQUEUE
+                    ]
+
+def dl_addr_to_str(a):
+    return "%x:%x:%x:%x:%x:%x" % tuple(a)
+
+def ip_addr_to_str(a, n):
+    if n is not None:
+        a = a & ~((1 << (32 - n)) - 1)
+    result = "%d.%d.%d.%d" % (a >> 24, \
+                              (a >> 16) & 0xff, \
+                              (a >> 8) & 0xff, \
+                              a & 0xff \
+                              )
+    if n is not None:
+        result = result + ("/%d" % (n))
+    return result
+    
+
+class Flow_Cfg:
+    # Members:
+    # - match
+    # - idle_timeout
+    # - hard_timeout
+    # - priority
+    # - action_list
+
+    def __init__(self):
+        self.priority        = 0
+        self.match           = parse.ofp_match()
+        self.match.wildcards = ofp.OFPFW_ALL
+        self.idle_timeout    = 0
+        self.hard_timeout    = 0
+        self.actions         = action_list.action_list()
+
+    # {pri, match} is considered a flow key
+    def key_equal(self, x):
+        if self.priority != x.priority:
+            return False
+        # TBD - Should this logic be moved to ofp_match.__eq__()?
+        if self.match.wildcards != x.match.wildcards:
+            return False
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_IN_PORT) == 0 \
+           and self.match.in_port != x.match.in_port:
+            return False
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_DL_DST) == 0 \
+           and self.match.dl_dst != x.match.dl_dst:
+            return False
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_DL_SRC) == 0 \
+           and self.match.dl_src != x.match.dl_src:
+            return False
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_DL_VLAN) == 0 \
+           and self.match.dl_vlan != x.match.dl_vlan:
+            return False
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_DL_VLAN_PCP) == 0 \
+           and self.match.dl_vlan_pcp != x.match.dl_vlan_pcp:
+            return False
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_DL_TYPE) == 0 \
+           and self.match.dl_type != x.match.dl_type:
+            return False
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_NW_TOS) == 0 \
+           and self.match.nw_tos != x.match.nw_tos:
+            return False
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_NW_PROTO) == 0 \
+           and self.match.nw_proto != x.match.nw_proto:
+            return False
+        n = wildcard_get(self.match.wildcards, ofp.OFPFW_NW_SRC_MASK)
+        if n < 32:
+            m = ~((1 << n) - 1)
+            if (self.match.nw_src & m) != (x.match.nw_src & m):
+                return False
+        n = wildcard_get(self.match.wildcards, ofp.OFPFW_NW_DST_MASK)
+        if n < 32:
+            m = ~((1 << n) - 1)
+            if (self.match.nw_dst & m) != (x.match.nw_dst & m):
+                return False
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_TP_SRC) == 0 \
+               and self.match.tp_src != x.match.tp_src:
+            return False
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_TP_DST) == 0 \
+               and self.match.tp_dst != x.match.tp_dst:
+            return False
+        return True
+
+    def non_key_equal(self, x):
+        if self.cookie != x.cookie:
+            return False
+        if self.idle_timeout != x.idle_timeout:
+            return False
+        if self.hard_timeout != x.hard_timeout:
+            return False
+        if True:
+            # For now, compare actions lists as unordered
+            aa = copy.deepcopy(x.actions.actions)
+            for a in self.actions.actions:
+                i = 0
+                while i < len(aa):
+                    if a == aa[i]:
+                        break
+                    i = i + 1
+                if i < len(aa):
+                    aa.pop(i)
+                else:
+                    return False
+            return aa == []
+        else:
+            return self.actions == x.actions
+        
+    def key_str(self):
+        result = "priority=%d" % self.priority
+        # TBD - Would be nice if ofp_match.show() was better behaved
+        # (no newlines), and more intuitive (things in hex where approprate), etc.
+        result = result + (", wildcards=0x%x={" % (self.match.wildcards))
+        sep = ""
+        for w in all_wildcards_list:
+            if (self.match.wildcards & w) == 0:
+                continue
+            if w == ofp.OFPFW_NW_SRC_MASK:
+                n = wildcard_get(self.match.wildcards, w)
+                if n > 0:
+                    result = result + sep + ("OFPFW_NW_SRC(%d)" % (n))
+            elif w == ofp.OFPFW_NW_DST_MASK:
+                n = wildcard_get(self.match.wildcards, w)
+                if n > 0:
+                    result = result + sep + ("OFPFW_NW_DST(%d)" % (n))
+            else:
+                result = result + sep + all_wildcard_names[w]
+            sep = ", "
+        result = result +"}"
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_IN_PORT) == 0:
+            result = result + (", in_port=%d" % (self.match.in_port))
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_DL_DST) == 0:
+            result = result + (", dl_dst=%s" \
+                               % (dl_addr_to_str(self.match.dl_dst)) \
+                               )
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_DL_SRC) == 0:
+            result = result + (", dl_src=%s" \
+                               % (dl_addr_to_str(self.match.dl_src)) \
+                               )
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_DL_VLAN) == 0:
+            result = result + (", dl_vlan=%d" % (self.match.dl_vlan))
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_DL_VLAN_PCP) == 0:
+            result = result + (", dl_vlan_pcp=%d" % (self.match.dl_vlan_pcp))
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_DL_TYPE) == 0:
+            result = result + (", dl_type=0x%x" % (self.match.dl_type))
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_NW_TOS) == 0:
+            result = result + (", nw_tos=0x%x" % (self.match.nw_tos))
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_NW_PROTO) == 0:
+            result = result + (", nw_proto=%d" % (self.match.nw_proto))
+        n = wildcard_get(self.match.wildcards, ofp.OFPFW_NW_SRC_MASK)
+        if n < 32:
+            result = result + (", nw_src=%s" % \
+                               (ip_addr_to_str(self.match.nw_src, 32 - n)) \
+                               )
+        n = wildcard_get(self.match.wildcards, ofp.OFPFW_NW_DST_MASK)
+        if n < 32:
+            result = result + (", nw_dst=%s" % \
+                               (ip_addr_to_str(self.match.nw_dst, 32 - n)) \
+                               )
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_TP_SRC) == 0:
+            result = result + (", tp_src=%d" % self.match.tp_src)
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_TP_DST) == 0:
+            result = result + (", tp_dst=%d" % self.match.tp_dst)
+        return result
+
+    def __eq__(self, x):
+        return (self.key_equal(x) and self.non_key_equal(x))
+
+    def __str__(self):
+        result = self.key_str()
+        result = result + (", cookie=%d" % self.cookie)
+        result = result + (", idle_timeout=%d" % self.idle_timeout)
+        result = result + (", hard_timeout=%d" % self.hard_timeout)
+        for a in self.actions.actions:
+            result = result + (", action=%s" % ofp.ofp_action_type_map[a.type])
+            if a.type == ofp.OFPAT_OUTPUT:
+                result = result + ("(%d)" % (a.port))
+            elif a.type == ofp.OFPAT_SET_VLAN_VID:
+                result = result + ("(%d)" % (a.vlan_vid))
+            elif a.type == ofp.OFPAT_SET_VLAN_PCP:
+                result = result + ("(%d)" % (a.vlan_pcp))
+            elif a.type == ofp.OFPAT_SET_DL_SRC or a.type == ofp.OFPAT_SET_DL_DST:
+                result = result + ("(%s)" % (dl_addr_to_str(a.dl_addr)))
+            elif a.type == ofp.OFPAT_SET_NW_SRC or a.type == ofp.OFPAT_SET_NW_DST:
+                result = result + ("(%s)" % (ip_addr_to_str(a.nw_addr, None)))
+            elif a.type == ofp.OFPAT_SET_NW_TOS:
+                result = result + ("(0x%x)" % (a.nw_tos))
+            elif a.type == ofp.OFPAT_SET_TP_SRC or a.type == ofp.OFPAT_SET_TP_DST:
+                result = result + ("(%d)" % (a.tp_port))
+            elif a.type == ofp.OFPAT_ENQUEUE:
+                result = result + ("(port=%d,queue=%d)" % (a.port, a.queue_id))
+        return result
+
+    def rand_actions_ordered(self, fi, valid_actions, valid_ports):
+        # Action lists are ordered, so pick an ordered random subset of
+        # supported actions
+        ACTION_MAX_LEN = 65535 # @fixme Should be test param?
+        supported_actions = []
+        for a in all_actions_list:
+            if ((1 << a) & valid_actions) != 0:
+                supported_actions.append(a)
+
+        supported_actions = shuffle(supported_actions)
+        supported_actions \
+            = supported_actions[0 : random.randint(1, len(supported_actions))]
+
+        self.actions = action_list.action_list()
+        for a in supported_actions:
+            act = None
+            if a == ofp.OFPAT_OUTPUT:
+                pass                    # OUTPUT actions must come last
+            elif a == ofp.OFPAT_SET_VLAN_VID:
+                act = action.action_set_vlan_vid()
+                act.vlan_vid = fi.rand_vlan()
+            elif a == ofp.OFPAT_SET_VLAN_PCP:
+                act = action.action_set_vlan_pcp()
+                act.vlan_pcp = random.randint(0, (1 << 3) - 1)
+            elif a == ofp.OFPAT_STRIP_VLAN:
+                act = action.action_strip_vlan()
+            elif a == ofp.OFPAT_SET_DL_SRC:
+                act = action.action_set_dl_src()
+                act.dl_addr = fi.rand_dl_addr()
+            elif a == ofp.OFPAT_SET_DL_DST:
+                act = action.action_set_dl_dst()
+                act.dl_addr = fi.rand_dl_addr()
+            elif a == ofp.OFPAT_SET_NW_SRC:
+                act = action.action_set_nw_src()
+                act.nw_addr = fi.rand_ip_addr()
+            elif a == ofp.OFPAT_SET_NW_DST:
+                act = action.action_set_nw_dst()
+                act.nw_addr = fi.rand_ip_addr()
+            elif a == ofp.OFPAT_SET_NW_TOS:
+                act = action.action_set_nw_tos()
+                act.nw_tos = fi.rand_ip_tos()
+            elif a == ofp.OFPAT_SET_TP_SRC:
+                act = action.action_set_tp_src()
+                act.tp_port = fi.rand_l4_port()
+            elif a == ofp.OFPAT_SET_TP_DST:
+                act = action.action_set_tp_dst()
+                act.tp_port = fi.rand_l4_port()
+            elif a == ofp.OFPAT_ENQUEUE:
+                pass                    # Enqueue actions must come last
+            if act:
+                act.max_len = ACTION_MAX_LEN
+                self.actions.add(act)
+                
+        p = random.randint(1, 100)
+        if p <= 33 and ofp.OFPAT_ENQUEUE in supported_actions:
+            # One third of the time, include ENQUEUE actions at end of list
+            # At most 1 ENQUEUE action
+            act = action.action_enqueue()
+            act.port = rand_pick(valid_ports)
+            # TBD - Limits for queue number?
+            act.queue_id = random.randint(0, 7)
+            act.max_len = ACTION_MAX_LEN
+            self.actions.add(act)
+        if p > 33 and p <= 66 and ofp.OFPAT_OUTPUT in supported_actions:
+            # One third of the time, include OUTPUT actions at end of list
+            port_idxs = shuffle(range(len(valid_ports)))
+            # Only 1 output action allowed if IN_PORT wildcarded
+            n = 1 if wildcard_get(self.match.wildcards, ofp.OFPFW_IN_PORT) != 0 \
+                else random.randint(1, len(valid_ports))
+            port_idxs = port_idxs[0 : n]
+            for pi in port_idxs:
+                act = action.action_output()
+                act.port = valid_ports[pi]
+                act.max_len = ACTION_MAX_LEN
+                if act.port != ofp.OFPP_IN_PORT \
+                   or wildcard_get(self.match.wildcards, ofp.OFPFW_IN_PORT) == 0:
+                    # OUTPUT(IN_PORT) only valid if OFPFW_IN_PORT not wildcarded
+                    self.actions.add(act)
+        else:
+            # One third of the time, include neither
+            pass
+
+
+    # Randomize flow data for flow modifies (i.e. cookie and actions)
+    def rand_mod(self, fi, valid_actions, valid_ports):
+        self.cookie = random.randint(0, (1 << 53) - 1)
+
+        # By default, test with conservative ordering conventions
+        # This should probably be indicated in a profile
+        if test_param_get(fq_config, "conservative_ordered_actions", True):
+            self.rand_actions_ordered(fi, valid_actions, valid_ports)
+            return self
+
+        # Action lists are ordered, so pick an ordered random subset of
+        # supported actions
+        supported_actions = []
+        for a in all_actions_list:
+            if ((1 << a) & valid_actions) != 0:
+                supported_actions.append(a)
+
+        supported_actions = shuffle(supported_actions)
+        supported_actions \
+            = supported_actions[0 : random.randint(1, len(supported_actions))]
+
+        self.actions = action_list.action_list()
+        for a in supported_actions:
+            if a == ofp.OFPAT_OUTPUT:
+                # TBD - Output actions are clustered in list, spread them out?
+                port_idxs = shuffle(range(len(valid_ports)))
+                port_idxs = port_idxs[0 : random.randint(1, len(valid_ports))]
+                for pi in port_idxs:
+                    act = action.action_output()
+                    act.port = valid_ports[pi]
+                    self.actions.add(act)
+            elif a == ofp.OFPAT_SET_VLAN_VID:
+                act = action.action_set_vlan_vid()
+                act.vlan_vid = fi.rand_vlan()
+                self.actions.add(act)
+            elif a == ofp.OFPAT_SET_VLAN_PCP:
+                act = action.action_set_vlan_pcp()
+                act.vlan_pcp = random.randint(0, (1 << 3) - 1)
+            elif a == ofp.OFPAT_STRIP_VLAN:
+                act = action.action_strip_vlan()
+                self.actions.add(act)
+            elif a == ofp.OFPAT_SET_DL_SRC:
+                act = action.action_set_dl_src()
+                act.dl_addr = fi.rand_dl_addr()
+                self.actions.add(act)
+            elif a == ofp.OFPAT_SET_DL_DST:
+                act = action.action_set_dl_dst()
+                act.dl_addr = fi.rand_dl_addr()
+                self.actions.add(act)
+            elif a == ofp.OFPAT_SET_NW_SRC:
+                act = action.action_set_nw_src()
+                act.nw_addr = fi.rand_ip_addr()
+                self.actions.add(act)
+            elif a == ofp.OFPAT_SET_NW_DST:
+                act = action.action_set_nw_dst()
+                act.nw_addr = fi.rand_ip_addr()
+                self.actions.add(act)
+            elif a == ofp.OFPAT_SET_NW_TOS:
+                act = action.action_set_nw_tos()
+                act.nw_tos = fi.rand_ip_tos()
+                self.actions.add(act)
+            elif a == ofp.OFPAT_SET_TP_SRC:
+                act = action.action_set_tp_src()
+                act.tp_port = fi.rand_l4_port()
+                self.actions.add(act)
+            elif a == ofp.OFPAT_SET_TP_DST:
+                act = action.action_set_tp_dst()
+                act.tp_port = fi.rand_l4_port()
+                self.actions.add(act)
+            elif a == ofp.OFPAT_ENQUEUE:
+                # TBD - Enqueue actions are clustered in list, spread them out?
+                port_idxs = shuffle(range(len(valid_ports)))
+                port_idxs = port_idxs[0 : random.randint(1, len(valid_ports))]
+                for pi in port_idxs:
+                    act = action.action_enqueue()
+                    act.port = valid_ports[pi]
+                    # TBD - Limits for queue number?
+                    act.queue_id = random.randint(0, 7)
+                    self.actions.add(act)
+
+        return self
+
+    # Randomize flow cfg
+    def rand(self, fi, valid_wildcards, valid_actions, valid_ports):
+        # Start with no wildcards, i.e. everything specified
+        self.match.wildcards = 0
+        
+        # Make approx. 5% of flows exact
+        exact = (random.randint(1, 100) <= 5)
+
+        # For each qualifier Q,
+        #   if (wildcarding is not supported for Q,
+        #       or an exact flow is specified
+        #       or a coin toss comes up heads), 
+        #      specify Q
+        #   else
+        #      wildcard Q
+
+        if wildcard_get(valid_wildcards, ofp.OFPFW_IN_PORT) == 0 \
+           or exact \
+           or flip_coin():
+            self.match.in_port = rand_pick(valid_ports)
+        else:
+            self.match.wildcards = wildcard_set(self.match.wildcards, \
+                                                ofp.OFPFW_IN_PORT, \
+                                                1 \
+                                                )
+            
+        if wildcard_get(valid_wildcards, ofp.OFPFW_DL_DST) == 0 \
+           or exact \
+           or flip_coin():
+            self.match.dl_dst = fi.rand_dl_addr()
+        else:
+            self.match.wildcards = wildcard_set(self.match.wildcards, \
+                                                ofp.OFPFW_DL_DST, \
+                                                1 \
+                                                )
+
+        if wildcard_get(valid_wildcards, ofp.OFPFW_DL_SRC) == 0 \
+           or exact \
+           or flip_coin():
+            self.match.dl_src = fi.rand_dl_addr()
+        else:
+            self.match.wildcards = wildcard_set(self.match.wildcards, \
+                                                ofp.OFPFW_DL_SRC, \
+                                                1 \
+                                                )
+
+        if wildcard_get(valid_wildcards, ofp.OFPFW_DL_VLAN_PCP) == 0 \
+           or exact \
+           or flip_coin():
+            self.match.dl_vlan_pcp = random.randint(0, (1 << 3) - 1)
+        else:
+            self.match.wildcards = wildcard_set(self.match.wildcards, \
+                                                ofp.OFPFW_DL_VLAN_PCP, \
+                                                1 \
+                                                )
+
+        if wildcard_get(valid_wildcards, ofp.OFPFW_DL_VLAN) == 0 \
+           or exact \
+           or flip_coin():
+            self.match.dl_vlan = fi.rand_vlan()
+        else:
+            self.match.wildcards = wildcard_set(self.match.wildcards, \
+                                                ofp.OFPFW_DL_VLAN, \
+                                                1 \
+                                                )
+
+        if wildcard_get(valid_wildcards, ofp.OFPFW_DL_TYPE) == 0 \
+           or exact \
+           or flip_coin():
+            self.match.dl_type = fi.rand_ethertype()
+        else:
+            self.match.wildcards = wildcard_set(self.match.wildcards, \
+                                                ofp.OFPFW_DL_TYPE, \
+                                                1 \
+                                                )
+
+        if exact or flip_coin():
+            n = 0
+        else:
+            n = wildcard_get(valid_wildcards, ofp.OFPFW_NW_SRC_MASK)
+            if n > 32:
+                n = 32
+            n = random.randint(0, n)
+        self.match.wildcards = wildcard_set(self.match.wildcards, \
+                                            ofp.OFPFW_NW_SRC_MASK, \
+                                            n \
+                                            )
+        if n < 32:
+            self.match.nw_src    = fi.rand_ip_addr() & ~((1 << n) - 1)
+            # Specifying any IP address match other than all bits
+            # don't care requires that Ethertype is one of {IP, ARP}
+            if flip_coin():
+                self.match.dl_type   = rand_pick([0x0800, 0x0806])
+                self.match.wildcards = wildcard_set(self.match.wildcards, \
+                                                    ofp.OFPFW_DL_TYPE, \
+                                                    0 \
+                                                    )
+
+        if exact or flip_coin():
+            n = 0
+        else:
+            n = wildcard_get(valid_wildcards, ofp.OFPFW_NW_DST_MASK)
+            if n > 32:
+                n = 32
+            n = random.randint(0, n)
+        self.match.wildcards = wildcard_set(self.match.wildcards, \
+                                            ofp.OFPFW_NW_DST_MASK, \
+                                            n \
+                                            )
+        if n < 32:
+            self.match.nw_dst    = fi.rand_ip_addr() & ~((1 << n) - 1)
+            # Specifying any IP address match other than all bits
+            # don't care requires that Ethertype is one of {IP, ARP}
+            if flip_coin():
+                self.match.dl_type   = rand_pick([0x0800, 0x0806])
+                self.match.wildcards = wildcard_set(self.match.wildcards, \
+                                                    ofp.OFPFW_DL_TYPE, \
+                                                    0 \
+                                                    )
+
+        if wildcard_get(valid_wildcards, ofp.OFPFW_NW_TOS) == 0 \
+           or exact \
+           or flip_coin():
+            self.match.nw_tos = fi.rand_ip_tos()
+            # Specifying a TOS value requires that Ethertype is IP
+            if flip_coin():
+                self.match.dl_type   = 0x0800
+                self.match.wildcards = wildcard_set(self.match.wildcards, \
+                                                    ofp.OFPFW_DL_TYPE, \
+                                                    0 \
+                                                    )
+        else:
+            self.match.wildcards = wildcard_set(self.match.wildcards, \
+                                                ofp.OFPFW_NW_TOS, \
+                                                1 \
+                                                )
+
+        # Known issue on OVS with specifying nw_proto w/o dl_type as IP
+        if wildcard_get(valid_wildcards, ofp.OFPFW_NW_PROTO) == 0 \
+               or exact \
+               or flip_coin():
+            self.match.nw_proto = fi.rand_ip_proto()
+            # Specifying an IP protocol requires that Ethertype is IP
+            if flip_coin():
+                self.match.dl_type   = 0x0800
+                self.match.wildcards = wildcard_set(self.match.wildcards, \
+                                                    ofp.OFPFW_DL_TYPE, \
+                                                    0 \
+                                                    )
+        else:            
+            self.match.wildcards = wildcard_set(self.match.wildcards, \
+                                                ofp.OFPFW_NW_PROTO, \
+                                                1 \
+                                                )
+
+        if wildcard_get(valid_wildcards, ofp.OFPFW_TP_SRC) == 0 \
+           or exact\
+           or flip_coin():
+            self.match.tp_src = fi.rand_l4_port()
+            # Specifying a L4 port requires that IP protcol is
+            # one of {ICMP, TCP, UDP}
+            if flip_coin():
+                self.match.nw_proto = rand_pick([1, 6, 17])
+                self.match.wildcards = wildcard_set(self.match.wildcards, \
+                                                    ofp.OFPFW_NW_PROTO, \
+                                                    0 \
+                                                    )
+                # Specifying a L4 port requirues that Ethertype is IP
+                self.match.dl_type   = 0x0800
+                self.match.wildcards = wildcard_set(self.match.wildcards, \
+                                                    ofp.OFPFW_DL_TYPE, \
+                                                    0 \
+                                                    )
+                if self.match.nw_proto == 1:
+                    self.match.tp_src = self.match.tp_src & 0xff
+        else:
+            self.match.wildcards = wildcard_set(self.match.wildcards, \
+                                                ofp.OFPFW_TP_SRC, \
+                                                1 \
+                                                )
+
+        if wildcard_get(valid_wildcards, ofp.OFPFW_TP_DST) == 0 \
+           or exact \
+           or flip_coin():
+            self.match.tp_dst = fi.rand_l4_port()
+            # Specifying a L4 port requires that IP protcol is
+            # one of {ICMP, TCP, UDP}
+            if flip_coin():
+                self.match.nw_proto = rand_pick([1, 6, 17])
+                self.match.wildcards = wildcard_set(self.match.wildcards, \
+                                                    ofp.OFPFW_NW_PROTO, \
+                                                    0 \
+                                                    )
+                # Specifying a L4 port requirues that Ethertype is IP
+                self.match.dl_type   = 0x0800
+                self.match.wildcards = wildcard_set(self.match.wildcards, \
+                                                    ofp.OFPFW_DL_TYPE, \
+                                                    0 \
+                                                    )
+                if self.match.nw_proto == 1:
+                    self.match.tp_dst = self.match.tp_dst & 0xff
+        else:
+            self.match.wildcards = wildcard_set(self.match.wildcards, \
+                                                ofp.OFPFW_TP_DST, \
+                                                1 \
+                                                )
+
+        # If nothing is wildcarded, it is an exact flow spec -- some switches
+        # (Open vSwitch, for one) *require* that exact flow specs
+        # have priority 65535.
+        self.priority = 65535 if self.match.wildcards == 0 \
+                        else fi.rand_priority()
+
+        # N.B. Don't make the timeout too short, else the flow might
+        # disappear before we get a chance to check for it.
+        t = random.randint(0, 65535)
+        self.idle_timeout = 0 if t < 60 else t
+        t = random.randint(0, 65535)
+        self.hard_timeout = 0 if t < 60 else t
+
+        self.rand_mod(fi, valid_actions, valid_ports)
+
+        return self
+
+    # Return flow cfg in canonical form
+    # - There are dependencies between flow qualifiers, e.g. it only makes
+    #   sense to qualify nw_proto if dl_type is qualified to be 0x0800 (IP).
+    #   The canonical form of flow match criteria will "wildcard out"
+    #   all such cases.
+    def canonical(self):
+        result = copy.deepcopy(self)
+        
+        if wildcard_get(result.match.wildcards, ofp.OFPFW_DL_VLAN) != 0:
+            result.match.wildcards = wildcard_set(result.match.wildcards, \
+                                                  ofp.OFPFW_DL_VLAN_PCP, \
+                                                  1 \
+                                                  )
+
+        if wildcard_get(result.match.wildcards, ofp.OFPFW_DL_TYPE) != 0 \
+               or result.match.dl_type not in [0x0800, 0x0806]:
+            # dl_tyoe is wildcarded, or specified as something other
+            # than IP or ARP
+            # => nw_src, nw_dst, nw_proto cannot be specified,
+            # must be wildcarded
+            result.match.wildcards = wildcard_set(result.match.wildcards, \
+                                                  ofp.OFPFW_NW_SRC_MASK, \
+                                                  32 \
+                                                  )
+            result.match.wildcards = wildcard_set(result.match.wildcards, \
+                                                  ofp.OFPFW_NW_DST_MASK, \
+                                                  32 \
+                                                  )
+            result.match.wildcards = wildcard_set(result.match.wildcards, \
+                                                  ofp.OFPFW_NW_PROTO, \
+                                                  1 \
+                                                  )
+
+        if wildcard_get(result.match.wildcards, ofp.OFPFW_DL_TYPE) != 0 \
+               or result.match.dl_type != 0x0800:
+            # dl_type is wildcarded, or specified as something other than IP
+            # => nw_tos, tp_src and tp_dst cannot be specified,
+            #    must be wildcarded
+            result.match.wildcards = wildcard_set(result.match.wildcards, \
+                                                  ofp.OFPFW_NW_TOS, \
+                                                  1 \
+                                                  )
+            result.match.wildcards = wildcard_set(result.match.wildcards, \
+                                                  ofp.OFPFW_TP_SRC, \
+                                                  1 \
+                                                  )
+            result.match.wildcards = wildcard_set(result.match.wildcards, \
+                                                  ofp.OFPFW_TP_DST, \
+                                                  1 \
+                                                  )
+            result.match.wildcards = wildcard_set(result.match.wildcards, \
+                                                  ofp.OFPFW_NW_SRC_MASK, \
+                                                  32 \
+                                                  )
+            result.match.wildcards = wildcard_set(result.match.wildcards, \
+                                                  ofp.OFPFW_NW_DST_MASK, \
+                                                  32 \
+                                                  )
+            result.match.wildcards = wildcard_set(result.match.wildcards, \
+                                                  ofp.OFPFW_NW_PROTO, \
+                                                  1 \
+                                                  )
+            
+        if wildcard_get(result.match.wildcards, ofp.OFPFW_NW_PROTO) != 0 \
+               or result.match.nw_proto not in [1, 6, 17]:
+            # nw_proto is wildcarded, or specified as something other than ICMP,
+            # TCP or UDP
+            # => tp_src and tp_dst cannot be specified, must be wildcarded
+            result.match.wildcards = wildcard_set(result.match.wildcards, \
+                                                  ofp.OFPFW_TP_SRC, \
+                                                  1 \
+                                                  )
+            result.match.wildcards = wildcard_set(result.match.wildcards, \
+                                                  ofp.OFPFW_TP_DST, \
+                                                  1 \
+                                                  )
+        return result
+
+    # Overlap check
+    # delf == True <=> Check for delete overlap, else add overlap
+    # "Add overlap" is defined as there exists a packet that could match both the
+    # receiver and argument flowspecs
+    # "Delete overlap" is defined as the specificity of the argument flowspec
+    # is greater than or equal to the specificity of the receiver flowspec
+    def overlaps(self, x, delf):
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_IN_PORT) == 0:
+            if wildcard_get(x.match.wildcards, ofp.OFPFW_IN_PORT) == 0:
+                if self.match.in_port != x.match.in_port:
+                    return False        # Both specified, and not equal
+            elif delf:
+                return False            # Receiver more specific
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_DL_VLAN) == 0:
+            if wildcard_get(x.match.wildcards, ofp.OFPFW_DL_VLAN) == 0:
+                if self.match.dl_vlan != x.match.dl_vlan:
+                    return False        # Both specified, and not equal
+            elif delf:
+                return False            # Receiver more specific
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_DL_SRC) == 0:
+            if wildcard_get(x.match.wildcards, ofp.OFPFW_DL_SRC) == 0:
+                if self.match.dl_src != x.match.dl_src:
+                    return False        # Both specified, and not equal
+            elif delf:
+                return False            # Receiver more specific
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_DL_DST) == 0:
+            if wildcard_get(x.match.wildcards, ofp.OFPFW_DL_DST) == 0:
+                if self.match.dl_dst != x.match.dl_dst:
+                    return False        # Both specified, and not equal
+            elif delf:
+                return False            # Receiver more specific
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_DL_TYPE) == 0:
+            if wildcard_get(x.match.wildcards, ofp.OFPFW_DL_TYPE) == 0:
+                if self.match.dl_type != x.match.dl_type:
+                    return False        # Both specified, and not equal
+            elif delf:
+                return False            # Recevier more specific
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_NW_PROTO) == 0:
+            if wildcard_get(x.match.wildcards, ofp.OFPFW_NW_PROTO) == 0:
+                if self.match.nw_proto != x.match.nw_proto:
+                    return False        # Both specified, and not equal
+            elif delf:
+                return False            # Receiver more specific
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_TP_SRC) == 0:
+            if wildcard_get(x.match.wildcards, ofp.OFPFW_TP_SRC) == 0:
+                if self.match.tp_src != x.match.tp_src:
+                    return False        # Both specified, and not equal
+            elif delf:
+                return False            # Receiver more specific
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_TP_DST) == 0:
+            if wildcard_get(x.match.wildcards, ofp.OFPFW_TP_DST) == 0:
+                if self.match.tp_dst != x.match.tp_dst:
+                    return False        # Both specified, and not equal
+            elif delf:
+                return False            # Receiver more specific
+        na = wildcard_get(self.match.wildcards, ofp.OFPFW_NW_SRC_MASK)
+        nb = wildcard_get(x.match.wildcards, ofp.OFPFW_NW_SRC_MASK)
+        if delf and na < nb:
+            return False                # Receiver more specific
+        if (na < 32 and nb < 32):
+            m = ~((1 << na) - 1) & ~((1 << nb) - 1)
+            if (self.match.nw_src & m) != (x.match.nw_src & m):
+                return False            # Overlapping bits not equal
+        na = wildcard_get(self.match.wildcards, ofp.OFPFW_NW_DST_MASK)
+        nb = wildcard_get(x.match.wildcards, ofp.OFPFW_NW_DST_MASK)
+        if delf and na < nb:
+            return False                # Receiver more specific
+        if (na < 32 and nb < 32):
+            m = ~((1 << na) - 1) & ~((1 << nb) - 1)
+            if (self.match.nw_dst & m) != (x.match.nw_dst & m):
+                return False            # Overlapping bits not equal
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_DL_VLAN_PCP) == 0:
+            if wildcard_get(x.match.wildcards, ofp.OFPFW_DL_VLAN_PCP) == 0:
+                if self.match.dl_vlan_pcp != x.match.dl_vlan_pcp:
+                    return False        # Both specified, and not equal
+            elif delf:
+                return False            # Receiver more specific
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_NW_TOS) == 0:
+            if wildcard_get(x.match.wildcards, ofp.OFPFW_NW_TOS) == 0:
+                if self.match.nw_tos != x.match.nw_tos:
+                    return False        # Both specified, and not equal
+            elif delf:
+                return False            # Receiver more specific
+        return True                     # Flows overlap
+
+    def to_flow_mod_msg(self, msg):
+        msg.match        = self.match
+        msg.cookie       = self.cookie
+        msg.idle_timeout = self.idle_timeout
+        msg.hard_timeout = self.hard_timeout
+        msg.priority     = self.priority
+        msg.actions      = self.actions
+        return msg
+
+    def from_flow_stat(self, msg):
+        self.match        = msg.match
+        self.cookie       = msg.cookie
+        self.idle_timeout = msg.idle_timeout
+        self.hard_timeout = msg.hard_timeout
+        self.priority     = msg.priority
+        self.actions      = msg.actions
+
+    def from_flow_rem(self, msg):
+        self.match        = msg.match
+        self.idle_timeout = msg.idle_timeout
+        self.priority     = msg.priority
+
+
+class Flow_Tbl:
+    def clear(self):
+        self.dict = {}
+
+    def __init__(self):
+        self.clear()
+
+    def find(self, f):
+        return self.dict.get(f.key_str(), None)
+
+    def insert(self, f):
+        self.dict[f.key_str()] = f
+
+    def delete(self, f):
+        del self.dict[f.key_str()]
+
+    def values(self):
+        return self.dict.values()
+
+    def count(self):
+        return len(self.dict)
+
+    def rand(self, sw, fi, num_flows):
+        self.clear()
+        i = 0
+        tbl = 0
+        j = 0
+        while i < num_flows:
+            fc = Flow_Cfg()
+            fc.rand(fi, \
+                    sw.tbl_stats.stats[tbl].wildcards, \
+                    sw.sw_features.actions, \
+                    sw.valid_ports \
+                    )
+            fc = fc.canonical()
+            if self.find(fc):
+                continue
+            fc.send_rem = False
+            self.insert(fc)
+            i = i + 1
+            j = j + 1
+            if j >= sw.tbl_stats.stats[tbl].max_entries:
+                tbl = tbl + 1
+                j = 0
+
+
+error_msgs   = []
+removed_msgs = []
+
+def error_handler(self, msg, rawmsg):
+    fq_logger.debug("Got an ERROR message, type=%d, code=%d" \
+                    % (msg.type, msg.code) \
+                    )
+    fq_logger.debug("Message header:")
+    fq_logger.debug(msg.header.show())
+    global error_msgs
+    error_msgs.append(msg)
+    pass
+
+def removed_handler(self, msg, rawmsg):
+    fq_logger.debug("Got a REMOVED message")
+    fq_logger.debug("Message header:")
+    fq_logger.debug(msg.header.show())
+    global removed_msgs
+    removed_msgs.append(msg)
+    pass
+
+class Switch:
+    # Members:
+    # controller  - switch's test controller
+    # sw_features - switch's OFPT_FEATURES_REPLY message
+    # valid_ports - list of valid port numbers
+    # tbl_stats   - switch's OFPT_STATS_REPLY message, for table stats request
+    # flow_stats  - switch's OFPT_STATS_REPLY message, for flow stats request
+    # flow_tbl    - (test's idea of) switch's flow table
+
+    def __init__(self):
+        self.controller  = None
+        self.sw_features = None
+        self.valid_ports = []
+        self.tbl_stats   = None
+        self.flow_stats  = None
+        self.flow_tbl    = Flow_Tbl()
+
+    def controller_set(self, controller):
+        self.controller = controller
+        # Register error message handler
+        global error_msgs
+        error_msgs = []
+        controller.register(ofp.OFPT_ERROR, error_handler)
+        controller.register(ofp.OFPT_FLOW_REMOVED, removed_handler)
+
+    def features_get(self):
+        # Get switch features
+        request = message.features_request()
+        (self.sw_features, pkt) = self.controller.transact(request, timeout=2)
+        if self.sw_features is None:
+            return False
+        self.valid_ports = map(lambda x: x.port_no, self.sw_features.ports)
+        # TBD - OFPP_LOCAL is returned by OVS is switch features --
+        # is that universal?
+
+        # TBD - There seems to be variability in which switches support which
+        # ports; need to sort that out
+        # TBD - Is it legal to enqueue to a special port?  Depends on switch?
+#         self.valid_ports.extend([ofp.OFPP_IN_PORT, \
+#                                  ofp.OFPP_NORMAL, \
+#                                  ofp.OFPP_FLOOD, \
+#                                  ofp.OFPP_ALL, \
+#                                  ofp.OFPP_CONTROLLER \
+#                                  ] \
+#                                 )
+        actions_override = test_param_get(fq_config, "actions", -1)
+        if actions_override != -1:
+            self.sw_features.actions = actions_override
+
+        return True
+
+    def tbl_stats_get(self):
+        # Get table stats
+        request = message.table_stats_request()
+        (self.tbl_stats, pkt) = self.controller.transact(request, timeout=2)
+        if self.tbl_stats is None:
+            return False
+        for ts in self.tbl_stats.stats:
+            wildcards_override = test_param_get(fq_config, "wildcards", -1)
+            if wildcards_override != -1:
+                ts.wildcards = wildcards_override
+        return True
+
+    def flow_stats_get(self):
+        request = message.flow_stats_request()
+        query_match           = ofp.ofp_match()
+        query_match.wildcards = ofp.OFPFW_ALL
+        request.match    = query_match
+        request.table_id = 0xff
+        request.out_port = ofp.OFPP_NONE;
+        if self.controller.message_send(request) == -1:
+            return False
+        # <TBD>
+        # Glue together successive reponse messages for stats reply.
+        # Looking at the "more" flag and performing re-assembly
+        # should be a part of the infrastructure.
+        # </TBD>
+        n = 0
+        while True:
+            # TBD - Check for "more" flag
+            (resp, pkt) = self.controller.poll(ofp.OFPT_STATS_REPLY, 4)
+            if resp is None:
+                break
+            if n == 0:
+                self.flow_stats = resp
+            else:
+                self.flow_stats.stats.extend(resp.stats)
+            n = n + 1
+        return (n > 0)
+
+    def flow_add(self, flow_cfg, overlapf = False):
+        flow_mod_msg = message.flow_mod()
+        flow_mod_msg.command     = ofp.OFPFC_ADD
+        flow_mod_msg.buffer_id   = 0xffffffff
+        flow_cfg.to_flow_mod_msg(flow_mod_msg)
+        if overlapf:
+            flow_mod_msg.flags = flow_mod_msg.flags | ofp.OFPFF_CHECK_OVERLAP
+        if flow_cfg.send_rem:
+            flow_mod_msg.flags = flow_mod_msg.flags | ofp.OFPFF_SEND_FLOW_REM
+        flow_mod_msg.header.xid = random.randrange(1,0xffffffff)
+        fq_logger.debug("Sending flow_mod(add), xid=%d" % (flow_mod_msg.header.xid))
+        return (self.controller.message_send(flow_mod_msg) != -1)
+
+    def flow_mod(self, flow_cfg, strictf):
+        flow_mod_msg = message.flow_mod()
+        flow_mod_msg.command     = ofp.OFPFC_MODIFY_STRICT if strictf \
+                                   else ofp.OFPFC_MODIFY
+        flow_mod_msg.buffer_id   = 0xffffffff
+        flow_cfg.to_flow_mod_msg(flow_mod_msg)
+        flow_mod_msg.header.xid = random.randrange(1,0xffffffff)
+        fq_logger.debug("Sending flow_mod(mod), xid=%d" % (flow_mod_msg.header.xid))
+        return (self.controller.message_send(flow_mod_msg) != -1)
+
+    def flow_del(self, flow_cfg, strictf):
+        flow_mod_msg = message.flow_mod()
+        flow_mod_msg.command     = ofp.OFPFC_DELETE_STRICT if strictf \
+                                   else ofp.OFPFC_DELETE
+        flow_mod_msg.buffer_id   = 0xffffffff
+        # TBD - "out_port" filtering of deletes needs to be tested
+        flow_mod_msg.out_port    = ofp.OFPP_NONE
+        flow_cfg.to_flow_mod_msg(flow_mod_msg)
+        flow_mod_msg.header.xid = random.randrange(1,0xffffffff)
+        fq_logger.debug("Sending flow_mod(del), xid=%d" % (flow_mod_msg.header.xid))
+        return (self.controller.message_send(flow_mod_msg) != -1)
+
+    def barrier(self):
+        barrier = message.barrier_request()
+        (resp, pkt) = self.controller.transact(barrier, 20)
+        return (resp is not None)
+
+    def errors_verify(self, num_exp, type = 0, code = 0):
+        result = True
+        global error_msgs
+        fq_logger.debug("Expecting %d error messages" % (num_exp))
+        num_got = len(error_msgs)
+        fq_logger.debug("Got %d error messages" % (num_got))
+        if num_got != num_exp:
+            fq_logger.error("Incorrect number of error messages received")
+            result = False
+        if num_exp == 0:
+            return result
+        elif num_exp == 1:
+            fq_logger.debug("Expecting error message, type=%d, code=%d" \
+                            % (type, code) \
+                            )
+            f = False
+            for e in error_msgs:
+                if e.type == type and e.code == code:
+                    fq_logger.debug("Got it")
+                    f = True
+            if not f:
+                fq_logger.error("Did not get it")
+                result = False
+        else:
+            fq_logger.error("Can't expect more than 1 error message type")
+            result = False
+        return result
+
+    def removed_verify(self, num_exp):
+        result = True
+        global removed_msgs
+        fq_logger.debug("Expecting %d removed messages" % (num_exp))
+        num_got = len(removed_msgs)
+        fq_logger.debug("Got %d removed messages" % (num_got))
+        if num_got != num_exp:
+            fq_logger.error("Incorrect number of removed messages received")
+            result = False
+        if num_exp < 2:
+            return result
+        fq_logger.error("Can't expect more than 1 error message type")
+        return False
+
+        
+#         fq_logger.debug("Expecting %d error messages" % (num))
+#         if num > 0:
+#             fq_logger.debug("with type=%d code=%d" % (type, code))            
+#         result = True
+#         n = 0
+#         while True:
+#             (errmsg, pkt) = self.controller.poll(ofp.OFPT_ERROR, 1)
+#             if errmsg is None:
+#                 break
+#             fq_logger.debug("Got error message, type=%d, code=%d" \
+#                               % (errmsg.type, errmsg.code) \
+#                               )
+#             if num == 0 or errmsg.type != type or errmsg.code != code:
+#                 fq_logger.debug("Unexpected error message")
+#                 result = False
+#             n = n + 1
+#         if n != num:
+#             fq_logger.error("Received %d error messages" % (n))
+#             result = False
+#         return result
+
+    def flow_tbl_verify(self):
+        result = True
+    
+        # Verify flow count in switch
+        fq_logger.debug("Reading table stats")
+        fq_logger.debug("Expecting %d flows" % (self.flow_tbl.count()))
+        if not self.tbl_stats_get():
+            fq_logger.error("Get table stats failed")
+            return False
+        n = 0
+        for ts in self.tbl_stats.stats:
+            n = n + ts.active_count
+        fq_logger.debug("Table stats reported %d active flows" \
+                          % (n) \
+                          )
+        if n != self.flow_tbl.count():
+            fq_logger.error("Incorrect number of active flows reported")
+            result = False
+    
+        # Read flows from switch
+        fq_logger.debug("Retrieving flows from switch")
+        fq_logger.debug("Expecting %d flows" % (self.flow_tbl.count()))
+        if not self.flow_stats_get():
+            fq_logger.error("Get flow stats failed")
+            return False
+        fq_logger.debug("Retrieved %d flows" % (len(self.flow_stats.stats)))
+    
+        # Verify flows returned by switch
+    
+        if len(self.flow_stats.stats) != self.flow_tbl.count():
+            fq_logger.error("Switch reported incorrect number of flows")
+            result = False
+    
+        fq_logger.debug("Verifying received flows")
+        for fc in self.flow_tbl.values():
+            fc.matched = False
+        for fs in self.flow_stats.stats:
+            flow_in = Flow_Cfg()
+            flow_in.from_flow_stat(fs)
+            fq_logger.debug("Received flow:")
+            fq_logger.debug(str(flow_in))
+            fc = self.flow_tbl.find(flow_in)
+            if fc is None:
+                fq_logger.error("does not match any defined flow")
+                result = False
+            elif fc.matched:
+                fq_logger.error("re-matches defined flow:")
+                fq_logger.debug(str(fc))
+                result = False
+            else:
+                fq_logger.debug("matched")
+                if not flow_in == fc:
+                    fq_logger.error("Non-key portions of flow do not match")
+                    result = False
+                fc.matched = True
+        for fc in self.flow_tbl.values():
+            if not fc.matched:
+                fq_logger.error("Defined flow:")
+                fq_logger.error(str(fc))
+                fq_logger.error("was not returned by switch")
+                result = False
+    
+        return result
+
+# FLOW ADD 5
+#
+# OVERVIEW
+# Add flows to switch, read back and verify flow configurations
+#
+# PURPOSE
+# - Test acceptance of flow adds
+# - Test ability of switch to process additions to flow table in random
+#   priority order
+# - Test correctness of flow configuration responses
+#
+# PARAMETERS
+#
+# Name: num_flows
+# Type: number
+# Description:
+# Number of flows to define; 0 => maximum number of flows, as determined
+# from switch capabilities
+# Default: 100
+#
+# PROCESS
+# 1. Delete all flows from switch
+# 2. Generate <num_flows> distinct flow configurations
+# 3. Send <num_flows> flow adds to switch, for flows generated in step 2 above
+# 4. Verify that no OFPT_ERROR responses were generated by switch
+# 5. Retrieve flow stats from switch
+# 6. Compare flow configurations returned by switch
+# 7. Test PASSED iff all flows sent to switch in step 3 above are returned
+#    in step 5 above; else test FAILED
+
+class Flow_Add_5(basic.SimpleProtocol):
+    """
+    Test FLOW_ADD_5 from draft top-half test plan
+    
+    INPUTS
+    num_flows - Number of flows to generate
+    """
+
+    def runTest(self):
+        fq_logger.debug("Flow_Add_5 TEST BEGIN")
+
+        num_flows = test_param_get(fq_config, "num_flows", 100)
+
+        # Clear all flows from switch
+
+        fq_logger.debug("Deleting all flows from switch")
+        rc = delete_all_flows(self.controller, fq_logger)
+        self.assertEqual(rc, 0, "Failed to delete all flows")
+
+        # Get switch capabilites
+
+        fq_logger.debug("Getting switch capabilities")        
+        sw = Switch()
+        sw.controller_set(self.controller)
+        self.assertTrue(sw.features_get(), "Get switch features failed")
+        self.assertTrue(sw.tbl_stats_get(), "Get table stats failed")
+
+        if num_flows == 0:
+            # Number of flows requested was 0
+            # => Generate max number of flows
+
+            for ts in sw.tbl_stats.stats:
+                num_flows = num_flows + ts.max_entries
+
+        fq_logger.debug("Generating %d flows" % (num_flows))        
+
+        # Dream up some flow information, i.e. space to chose from for
+        # random flow parameter generation
+
+        fi = Flow_Info()
+        fi.rand(max(2 * int(math.log(num_flows)), 1))
+
+        # Create a flow table
+
+        ft = Flow_Tbl()
+        ft.rand(sw, fi, num_flows)
+
+        # Send flow table to switch
+
+        fq_logger.debug("Sending flow adds to switch")
+        for fc in ft.values():          # Randomizes order of sending
+            fq_logger.debug("Adding flow:")
+            fq_logger.debug(str(fc));
+            self.assertTrue(sw.flow_add(fc), "Failed to add flow")
+
+        # Do barrier, to make sure all flows are in
+
+        self.assertTrue(sw.barrier(), "Barrier failed")
+
+        result = True
+
+        # Check for any error messages
+
+        if not sw.errors_verify(0):
+            result = False
+
+        # Verify flow table
+
+        sw.flow_tbl = ft
+        if not sw.flow_tbl_verify():
+            result = False
+
+        self.assertTrue(result, "Flow_Add_5 TEST FAILED")
+        fq_logger.debug("Flow_Add_5 TEST PASSED")
+
+
+# FLOW ADD 5_1
+#
+# OVERVIEW
+# Verify handling of non-canonical flows
+#
+# PURPOSE
+# - Test that switch detects and correctly responds to a non-canonical flow
+#   definition.  A canonical flow is one that satisfies all match qualifier
+#   dependencies; a non-canonical flow is one that does not.
+#
+# PARAMETERS
+# - None
+#
+# PROCESS
+# 1. Delete all flows from switch
+# 2. Generate 1 flow definition, which is different from its canonicalization
+# 3. Send flow to switch
+# 4. Retrieve flow from switch
+# 5. Compare returned flow to canonical form of defined flow
+# 7. Test PASSED iff flow received in step 4 above is identical to canonical form of flow defined in step 3 above
+
+# Disabled.
+# Should be DUT dependent.
+test_prio["Flow_Add_5_1"] = -1
+
+class Flow_Add_5_1(basic.SimpleProtocol):
+    """
+    Test FLOW_ADD_5.1 from draft top-half test plan
+
+    INPUTS
+    None
+    """
+    
+    def runTest(self):
+        fq_logger.debug("Flow_Add_5_1 TEST BEGIN")
+
+        num_flows = test_param_get(fq_config, "num_flows", 100)
+        
+        # Clear all flows from switch
+
+        fq_logger.debug("Deleting all flows from switch")
+        rc = delete_all_flows(self.controller, fq_logger)
+        self.assertEqual(rc, 0, "Failed to delete all flows")
+
+        # Get switch capabilites
+
+        fq_logger.debug("Getting switch capabilities")        
+        sw = Switch()
+        sw.controller_set(self.controller)
+        self.assertTrue(sw.features_get(), "Get switch features failed")
+        self.assertTrue(sw.tbl_stats_get(), "Get table stats failed")
+
+        # Dream up some flow information, i.e. space to chose from for
+        # random flow parameter generation
+
+        fi = Flow_Info()
+        fi.rand(10)
+        
+        # Dream up a flow config that will be canonicalized by the switch
+
+        while True:
+            fc = Flow_Cfg()
+            fc.rand(fi, \
+                    sw.tbl_stats.stats[0].wildcards, \
+                    sw.sw_features.actions, \
+                    sw.valid_ports \
+                    )
+            fcc = fc.canonical()
+            if fcc.match != fc.match:
+                break
+
+        ft = Flow_Tbl()
+        ft.insert(fcc)
+
+        # Send it to the switch
+
+        fq_logger.debug("Sending flow add to switch:")
+        fq_logger.debug(str(fc))
+        fq_logger.debug("should be canonicalized as:")
+        fq_logger.debug(str(fcc))
+        fc.send_rem = False
+        self.assertTrue(sw.flow_add(fc), "Failed to add flow")
+
+        # Do barrier, to make sure all flows are in
+
+        self.assertTrue(sw.barrier(), "Barrier failed")
+
+        result = True
+
+        # Check for any error messages
+
+        if not sw.errors_verify(0):
+            result = False
+
+        # Verify flow table
+
+        sw.flow_tbl = ft
+        if not sw.flow_tbl_verify():
+            result = False
+
+        self.assertTrue(result, "Flow_Add_5_1 TEST FAILED")
+        fq_logger.debug("Flow_Add_5_1 TEST PASSED")
+
+
+# FLOW ADD 6
+#
+# OVERVIEW
+# Test flow table capacity
+#
+# PURPOSE
+# - Test switch can accept as many flow definitions as it claims
+# - Test generation of OFPET_FLOW_MOD_FAILED/OFPFMFC_ALL_TABLES_FULL
+# - Test that attempting to create flows beyond capacity does not corrupt
+#   flow table
+#
+# PARAMETERS
+# None
+#
+# PROCESS
+# 1. Delete all flows from switch
+# 2. Send OFPT_FEATURES_REQUEST and OFPT_STATS_REQUEST/OFPST_TABLE enquiries
+#    to determine flow table size, N
+# 3. Generate (N + 1) distinct flow configurations
+# 4. Send N flow adds to switch, for flows generated in step 3 above
+# 5. Verify flow table in switch
+# 6. Send one more flow add to switch
+# 7. Verify that OFPET_FLOW_MOD_FAILED/OFPFMFC_ALL_TABLES_FULL error
+#    response was generated by switch, for last flow mod sent
+# 7. Retrieve flow stats from switch
+# 8. Verify flow table in switch
+# 9. Test PASSED iff:
+#    - error message received, for correct flow
+#    - last flow definition sent to switch is not in flow table
+#    else test FAILED
+
+# Disabled because of bogus capacity reported by OVS.
+# Should be DUT dependent.
+test_prio["Flow_Add_6"] = -1
+
+class Flow_Add_6(basic.SimpleProtocol):
+    """
+    Test FLOW_ADD_6 from draft top-half test plan
+    
+    INPUTS
+    num_flows - Number of flows to generate
+    """
+
+    def runTest(self):
+        fq_logger.debug("Flow_Add_6 TEST BEGIN")
+
+        # Clear all flows from switch
+
+        fq_logger.debug("Deleting all flows from switch")
+        rc = delete_all_flows(self.controller, fq_logger)
+        self.assertEqual(rc, 0, "Failed to delete all flows")
+
+        # Get switch capabilites
+
+        fq_logger.debug("Getting switch capabilities")        
+        sw = Switch()
+        sw.controller_set(self.controller)
+        self.assertTrue(sw.features_get(), "Get switch features failed")
+        self.assertTrue(sw.tbl_stats_get(), "Get table stats failed")
+
+        num_flows = 0
+        for ts in sw.tbl_stats.stats:
+            num_flows = num_flows + ts.max_entries
+
+        fq_logger.debug("Switch capacity is %d flows" % (num_flows))        
+        fq_logger.debug("Generating %d flows" % (num_flows))        
+
+        # Dream up some flow information, i.e. space to chose from for
+        # random flow parameter generation
+
+        fi = Flow_Info()
+        fi.rand(max(2 * int(math.log(num_flows)), 1))
+
+        # Create a flow table, to switch's capacity
+
+        ft = Flow_Tbl()
+        ft.rand(sw, fi, num_flows)
+
+        # Send flow table to switch
+
+        fq_logger.debug("Sending flow adds to switch")
+        for fc in ft.values():          # Randomizes order of sending
+            fq_logger.debug("Adding flow:")
+            fq_logger.debug(str(fc));
+            self.assertTrue(sw.flow_add(fc), "Failed to add flow")
+
+        # Do barrier, to make sure all flows are in
+
+        self.assertTrue(sw.barrier(), "Barrier failed")
+
+        result = True
+
+        # Check for any error messages
+
+        if not sw.errors_verify(0):
+            result = False
+
+        # Dream up one more flow
+
+        fq_logger.debug("Creating one more flow")
+        while True:
+            fc = Flow_Cfg()
+            fc.rand(fi, \
+                    sw.tbl_stats.stats[0].wildcards, \
+                    sw.sw_features.actions, \
+                    sw.valid_ports \
+                    )
+            fc = fc.canonical()
+            if not ft.find(fc):
+                break
+
+        # Send one-more flow
+
+        fc.send_rem = False
+        fq_logger.debug("Sending flow add switch")
+        fq_logger.debug(str(fc));
+        self.assertTrue(sw.flow_add(fc), "Failed to add flow")
+
+        # Do barrier, to make sure all flows are in
+
+        self.assertTrue(sw.barrier(), "Barrier failed")
+
+        # Check for expected error message
+
+        if not sw.errors_verify(1, \
+                                ofp.OFPET_FLOW_MOD_FAILED, \
+                                ofp.OFPFMFC_ALL_TABLES_FULL \
+                                ):
+            result = False
+
+        # Verify flow table
+
+        sw.flow_tbl = ft
+        if not sw.flow_tbl_verify():
+            result = False
+
+        self.assertTrue(result, "Flow_add_6 TEST FAILED")
+        fq_logger.debug("Flow_add_6 TEST PASSED")
+
+
+# FLOW ADD 7
+#
+# OVERVIEW
+# Test flow redefinition
+#
+# PURPOSE
+# Verify that successive flow adds with same priority and match criteria
+# overwrite in flow table
+#
+# PARAMETERS
+# None
+#
+# PROCESS
+# 1. Delete all flows from switch
+# 2. Generate flow definition F1
+# 3. Generate flow definition F2, with same key (priority and match) as F1,
+#    but with different actions
+# 4. Send flow adds for F1 and F2 to switch
+# 5. Verify flow definitions in switch
+# 6. Test PASSED iff 1 flow returned by switch, matching configuration of F2;
+#    else test FAILED
+
+class Flow_Add_7(basic.SimpleProtocol):
+    """
+    Test FLOW_ADD_7 from draft top-half test plan
+    
+    INPUTS
+    None
+    """
+
+    def runTest(self):
+        fq_logger.debug("Flow_Add_7 TEST BEGIN")
+
+        # Clear all flows from switch
+
+        fq_logger.debug("Deleting all flows from switch")
+        rc = delete_all_flows(self.controller, fq_logger)
+        self.assertEqual(rc, 0, "Failed to delete all flows")
+
+        # Get switch capabilites
+
+        fq_logger.debug("Getting switch capabilities")        
+        sw = Switch()
+        sw.controller_set(self.controller)
+        self.assertTrue(sw.features_get(), "Get switch features failed")
+        self.assertTrue(sw.tbl_stats_get(), "Get table stats failed")
+
+        # Dream up some flow information, i.e. space to chose from for
+        # random flow parameter generation
+
+        fi = Flow_Info()
+        fi.rand(10)
+        
+        # Dream up a flow config
+
+        fc = Flow_Cfg()
+        fc.rand(fi, \
+                sw.tbl_stats.stats[0].wildcards, \
+                sw.sw_features.actions, \
+                sw.valid_ports \
+                )
+        fc = fc.canonical()
+
+        # Send it to the switch
+
+        fq_logger.debug("Sending flow add to switch:")
+        fq_logger.debug(str(fc))
+        ft = Flow_Tbl()
+        fc.send_rem = False
+        self.assertTrue(sw.flow_add(fc), "Failed to add flow")
+        ft.insert(fc)
+
+        # Dream up some different actions, with the same flow key
+
+        fc2 = copy.deepcopy(fc)
+        while True:
+            fc2.rand_mod(fi, \
+                         sw.sw_features.actions, \
+                         sw.valid_ports \
+                         )
+            if fc2 != fc:
+                break
+
+        # Send that to the switch
+        
+        fq_logger.debug("Sending flow add to switch:")
+        fq_logger.debug(str(fc2))
+        fc2.send_rem = False
+        self.assertTrue(sw.flow_add(fc2), "Failed to add flow")
+        ft.insert(fc2)
+
+        # Do barrier, to make sure all flows are in
+
+        self.assertTrue(sw.barrier(), "Barrier failed")
+
+        result = True
+
+        # Check for any error messages
+
+        if not sw.errors_verify(0):
+            result = False
+
+        # Verify flow table
+
+        sw.flow_tbl = ft
+        if not sw.flow_tbl_verify():
+            result = False
+
+        self.assertTrue(result, "Flow_Add_7 TEST FAILED")
+        fq_logger.debug("Flow_Add_7 TEST PASSED")
+
+
+# FLOW ADD 8
+#
+# OVERVIEW
+# Add overlapping flows to switch, verify that overlapping flows are rejected
+#
+# PURPOSE
+# - Test detection of overlapping flows by switch
+# - Test generation of OFPET_FLOW_MOD_FAILED/OFPFMFC_OVERLAP messages
+# - Test rejection of overlapping flows
+# - Test defining overlapping flows does not corrupt flow table
+#
+# PARAMETERS
+# None
+#
+# PROCESS
+# 1. Delete all flows from switch
+# 2. Generate flow definition F1
+# 3. Generate flow definition F2, with key overlapping F1
+# 4. Send flow add to switch, for F1
+# 5. Send flow add to switch, for F2, with OFPFF_CHECK_OVERLAP set
+# 6. Verify that OFPET_FLOW_MOD_FAILED/OFPFMFC_OVERLAP error response
+#    was generated by switch
+# 7. Verifiy flows configured in swtich
+# 8. Test PASSED iff:
+#    - error message received, for overlapping flow
+#    - overlapping flow is not in flow table
+#    else test FAILED
+
+class Flow_Add_8(basic.SimpleProtocol):
+    """
+    Test FLOW_ADD_8 from draft top-half test plan
+    
+    INPUTS
+    None
+    """
+
+    def runTest(self):
+        fq_logger.debug("Flow_Add_8 TEST BEGIN")
+
+        # Clear all flows from switch
+
+        fq_logger.debug("Deleting all flows from switch")
+        rc = delete_all_flows(self.controller, fq_logger)
+        self.assertEqual(rc, 0, "Failed to delete all flows")
+
+        # Get switch capabilites
+
+        fq_logger.debug("Getting switch capabilities")        
+        sw = Switch()
+        sw.controller_set(self.controller)
+        self.assertTrue(sw.features_get(), "Get switch features failed")
+        self.assertTrue(sw.tbl_stats_get(), "Get table stats failed")
+
+        # Dream up some flow information, i.e. space to chose from for
+        # random flow parameter generation
+
+        fi = Flow_Info()
+        fi.rand(10)
+        
+        # Dream up a flow config, with at least 1 qualifier specified
+
+        fc = Flow_Cfg()
+        while True:
+            fc.rand(fi, \
+                    sw.tbl_stats.stats[0].wildcards, \
+                    sw.sw_features.actions, \
+                    sw.valid_ports \
+                    )
+            fc = fc.canonical()
+            if fc.match.wildcards != ofp.OFPFW_ALL:
+                break
+
+        # Send it to the switch
+
+        fq_logger.debug("Sending flow add to switch:")
+        fq_logger.debug(str(fc))
+        ft = Flow_Tbl()
+        fc.send_rem = False
+        self.assertTrue(sw.flow_add(fc), "Failed to add flow")
+        ft.insert(fc)
+
+        # Wildcard out one qualifier that was specified, to create an
+        # overlapping flow
+
+        fc2 = copy.deepcopy(fc)
+        for wi in shuffle(range(len(all_wildcards_list))):
+            w = all_wildcards_list[wi]
+            if (fc2.match.wildcards & w) == 0:
+                break
+        if w == ofp.OFPFW_NW_SRC_MASK:
+            w  = ofp.OFPFW_NW_SRC_ALL
+            wn = "OFPFW_NW_SRC"
+        elif w == ofp.OFPFW_NW_DST_MASK:
+            w  = ofp.OFPFW_NW_DST_ALL
+            wn = "OFPFW_NW_DST"
+        else:
+            wn = all_wildcard_names[w]
+        fq_logger.debug("Wildcarding out %s" % (wn))
+        fc2.match.wildcards = fc2.match.wildcards | w
+        fc2 = fc2.canonical()
+
+        # Send that to the switch, with overlap checking
+        
+        fq_logger.debug("Sending flow add to switch:")
+        fq_logger.debug(str(fc2))
+        fc2.send_rem = False
+        self.assertTrue(sw.flow_add(fc2, True), "Failed to add flow")
+
+        # Do barrier, to make sure all flows are in
+        self.assertTrue(sw.barrier(), "Barrier failed")
+
+        result = True
+
+        # Check for expected error message
+
+        if not sw.errors_verify(1, \
+                                ofp.OFPET_FLOW_MOD_FAILED, \
+                                ofp.OFPFMFC_OVERLAP \
+                                ):
+            result = False
+
+        # Verify flow table
+
+        sw.flow_tbl = ft
+        if not sw.flow_tbl_verify():
+            result = False
+
+        self.assertTrue(result, "Flow_Add_8 TEST FAILED")
+        fq_logger.debug("Flow_Add_8 TEST PASSED")
+
+
+# FLOW MODIFY 1
+#
+# OVERVIEW
+# Strict modify of single existing flow
+#
+# PURPOSE
+# - Verify that strict flow modify operates only on specified flow
+# - Verify that flow is correctly modified
+#
+# PARAMETERS
+# None
+#
+# PROCESS
+# 1. Delete all flows from switch
+# 2. Generate 1 flow F
+# 3. Send flow add to switch, for flow F
+# 4. Generate new action list for flow F, yielding F'
+# 5. Send strict flow modify to switch, for flow F'
+# 6. Verify flow table in switch
+# 7. Test PASSED iff flow returned by switch is F'; else FAILED
+
+class Flow_Mod_1(basic.SimpleProtocol):
+    """
+    Test FLOW_MOD_1 from draft top-half test plan
+    
+    INPUTS
+    None
+    """
+
+    def runTest(self):
+        fq_logger.debug("Flow_Mod_1 TEST BEGIN")
+
+        # Clear all flows from switch
+
+        fq_logger.debug("Deleting all flows from switch")
+        rc = delete_all_flows(self.controller, fq_logger)
+        self.assertEqual(rc, 0, "Failed to delete all flows")
+
+        # Get switch capabilites
+
+        fq_logger.debug("Getting switch capabilities")        
+        sw = Switch()
+        sw.controller_set(self.controller)
+        self.assertTrue(sw.features_get(), "Get switch features failed")
+        self.assertTrue(sw.tbl_stats_get(), "Get table stats failed")
+
+        # Dream up some flow information, i.e. space to chose from for
+        # random flow parameter generation
+
+        fi = Flow_Info()
+        fi.rand(10)
+        
+        # Dream up a flow config
+
+        fc = Flow_Cfg()
+        fc.rand(fi, \
+                sw.tbl_stats.stats[0].wildcards, \
+                sw.sw_features.actions, \
+                sw.valid_ports \
+                )
+        fc = fc.canonical()
+
+        # Send it to the switch
+
+        fq_logger.debug("Sending flow add to switch:")
+        fq_logger.debug(str(fc))
+        ft = Flow_Tbl()
+        fc.send_rem = False
+        self.assertTrue(sw.flow_add(fc), "Failed to add flow")
+        ft.insert(fc)
+
+        # Dream up some different actions, with the same flow key
+
+        fc2 = copy.deepcopy(fc)
+        while True:
+            fc2.rand_mod(fi, \
+                         sw.sw_features.actions, \
+                         sw.valid_ports \
+                         )
+            if fc2 != fc:
+                break
+
+        fc2.cookie = fc.cookie
+
+        # Send that to the switch
+        
+        fq_logger.debug("Sending strict flow mod to switch:")
+        fq_logger.debug(str(fc2))
+        fc2.send_rem = False
+        self.assertTrue(sw.flow_mod(fc2, True), "Failed to modify flow")
+        ft.insert(fc2)
+
+        # Do barrier, to make sure all flows are in
+
+        self.assertTrue(sw.barrier(), "Barrier failed")
+
+        result = True
+
+        # Check for any error messages
+
+        if not sw.errors_verify(0):
+            result = False
+
+        # Verify flow table
+
+        sw.flow_tbl = ft
+        if not sw.flow_tbl_verify():
+            result = False
+
+        self.assertTrue(result, "Flow_Mod_1 TEST FAILED")
+        fq_logger.debug("Flow_Mod_1 TEST PASSED")
+
+
+# FLOW MODIFY 2
+#
+# OVERVIEW
+# Loose modify of mutiple flows
+#
+# PURPOSE
+# - Verify that loose flow modify operates only on matching flows
+# - Verify that matching flows are correctly modified
+#
+# PARAMETERS
+# Name: num_flows
+# Type: number
+# Description:
+# Number of flows to define
+# Default: 100
+#
+# PROCESS
+# 1. Delete all flows from switch
+# 2. Generate <num_flows> distinct flow configurations
+# 3. Send <num_flows> flow adds to switch
+# 4. Pick 1 defined flow F at random
+# 5. Create overlapping loose flow mod match criteria by repeatedly
+#    wildcarding out qualifiers in match of F => F',
+#    and create new actions list A' for F'
+# 6. Send loose flow modify for F' to switch
+# 7. Verify flow table in swtich
+# 8. Test PASSED iff all flows sent to switch in steps 3 and 6 above,
+#    are returned in step 7 above, each with correct (original or modified)
+#    action list;
+#    else test FAILED
+        
+class Flow_Mod_2(basic.SimpleProtocol):
+    """
+    Test FLOW_MOD_2 from draft top-half test plan
+    
+    INPUTS
+    None
+    """
+
+    def runTest(self):
+        fq_logger.debug("Flow_Mod_2 TEST BEGIN")
+
+        num_flows = test_param_get(fq_config, "num_flows", 100)
+
+        # Clear all flows from switch
+
+        fq_logger.debug("Deleting all flows from switch")
+        rc = delete_all_flows(self.controller, fq_logger)
+        self.assertEqual(rc, 0, "Failed to delete all flows")
+
+        # Get switch capabilites
+
+        fq_logger.debug("Getting switch capabilities")        
+        sw = Switch()
+        sw.controller_set(self.controller)
+        self.assertTrue(sw.features_get(), "Get switch features failed")
+        self.assertTrue(sw.tbl_stats_get(), "Get table stats failed")
+
+        # Dream up some flow information, i.e. space to chose from for
+        # random flow parameter generation
+
+        fi = Flow_Info()
+        # Shrunk, to increase chance of meta-matches
+        fi.rand(max(int(math.log(num_flows)) / 2, 1))
+        
+        # Dream up some flows
+
+        ft = Flow_Tbl()
+        ft.rand(sw, fi, num_flows)
+
+        # Send flow table to switch
+
+        fq_logger.debug("Sending flow adds to switch")
+        for fc in ft.values():          # Randomizes order of sending
+            fq_logger.debug("Adding flow:")
+            fq_logger.debug(str(fc));
+            self.assertTrue(sw.flow_add(fc), "Failed to add flow")
+
+        # Do barrier, to make sure all flows are in
+
+        self.assertTrue(sw.barrier(), "Barrier failed")
+
+        result = True
+
+        # Check for any error messages
+
+        if not sw.errors_verify(0):
+            result = False
+
+        # Verify flow table
+
+        sw.flow_tbl = ft
+        if not sw.flow_tbl_verify():
+            result = False
+
+        # Pick a random flow as a basis
+        
+        mfc = copy.deepcopy(ft.values()[0])
+        mfc.rand_mod(fi, sw.sw_features.actions, sw.valid_ports)
+
+        # Repeatedly wildcard qualifiers
+
+        for wi in shuffle(range(len(all_wildcards_list))):
+            w = all_wildcards_list[wi]
+            if w == ofp.OFPFW_NW_SRC_MASK or w == ofp.OFPFW_NW_DST_MASK:
+                n = wildcard_get(mfc.match.wildcards, w)
+                if n < 32:
+                    mfc.match.wildcards = wildcard_set(mfc.match.wildcards, \
+                                                       w, \
+                                                       random.randint(n + 1, 32) \
+                                                       )
+                else:
+                    continue
+            else:
+                if wildcard_get(mfc.match.wildcards, w) == 0:
+                    mfc.match.wildcards = wildcard_set(mfc.match.wildcards, w, 1)
+                else:
+                    continue
+            mfc = mfc.canonical()
+
+            # Count the number of flows that would be modified
+
+            n = 0
+            for fc in ft.values():
+                if mfc.overlaps(fc, True) and not mfc.non_key_equal(fc):
+                    n = n + 1
+
+            # If more than 1, we found our loose delete flow spec
+            if n > 1:
+                break
+                    
+        fq_logger.debug("Modifying %d flows" % (n))
+        fq_logger.debug("Sending flow mod to switch:")
+        fq_logger.debug(str(mfc))
+        self.assertTrue(sw.flow_mod(mfc, False), "Failed to modify flow")
+
+        # Do barrier, to make sure all flows are in
+        self.assertTrue(sw.barrier(), "Barrier failed")
+
+        # Check for error message
+
+        if not sw.errors_verify(0):
+            result = False
+
+        # Apply flow mod to local flow table
+
+        for fc in ft.values():
+            if mfc.overlaps(fc, True):
+                fc.actions = mfc.actions
+
+        # Verify flow table
+
+        sw.flow_tbl = ft
+        if not sw.flow_tbl_verify():
+            result = False
+
+        self.assertTrue(result, "Flow_Mod_2 TEST FAILED")
+        fq_logger.debug("Flow_Mod_2 TEST PASSED")
+
+
+# FLOW MODIFY 3
+
+# OVERVIEW
+# Strict modify of non-existent flow
+#
+# PURPOSE
+# Verify that strict modify of a non-existent flow is equivalent to a flow add
+#
+# PARAMETERS
+# None
+#
+# PROCESS
+# 1. Delete all flows from switch
+# 2. Send single flow mod, as strict modify, to switch
+# 3. Verify flow table in switch
+# 4. Test PASSED iff flow defined in step 2 above verified; else FAILED
+
+class Flow_Mod_3(basic.SimpleProtocol):
+    """
+    Test FLOW_MOD_3 from draft top-half test plan
+    
+    INPUTS
+    None
+    """
+
+    def runTest(self):
+        fq_logger.debug("Flow_Mod_3 TEST BEGIN")
+
+        # Clear all flows from switch
+
+        fq_logger.debug("Deleting all flows from switch")
+        rc = delete_all_flows(self.controller, fq_logger)
+        self.assertEqual(rc, 0, "Failed to delete all flows")
+
+        # Get switch capabilites
+
+        fq_logger.debug("Getting switch capabilities")        
+        sw = Switch()
+        sw.controller_set(self.controller)
+        self.assertTrue(sw.features_get(), "Get switch features failed")
+        self.assertTrue(sw.tbl_stats_get(), "Get table stats failed")
+
+        # Dream up some flow information, i.e. space to chose from for
+        # random flow parameter generation
+
+        fi = Flow_Info()
+        fi.rand(10)
+        
+        # Dream up a flow config
+
+        fc = Flow_Cfg()
+        fc.rand(fi, \
+                sw.tbl_stats.stats[0].wildcards, \
+                sw.sw_features.actions, \
+                sw.valid_ports \
+                )
+        fc = fc.canonical()
+
+        # Send it to the switch
+
+        fq_logger.debug("Sending flow mod to switch:")
+        fq_logger.debug(str(fc))
+        ft = Flow_Tbl()
+        fc.send_rem = False
+        self.assertTrue(sw.flow_mod(fc, True), "Failed to modify flows")
+        ft.insert(fc)
+
+        # Do barrier, to make sure all flows are in
+
+        self.assertTrue(sw.barrier(), "Barrier failed")
+
+        result = True
+
+        # Check for any error messages
+
+        if not sw.errors_verify(0):
+            result = False
+
+        # Verify flow table
+
+        sw.flow_tbl = ft
+        if not sw.flow_tbl_verify():
+            result = False
+
+        self.assertTrue(result, "Flow_Mod_3 TEST FAILED")
+        fq_logger.debug("Flow_Mod_3 TEST PASSED")
+
+
+# FLOW DELETE 1
+#
+# OVERVIEW
+# Strict delete of single flow
+#
+# PURPOSE
+# Verify correct operation of strict delete of single defined flow
+#
+# PARAMETERS
+# None
+#
+# PROCESS
+# 1. Delete all flows from switch
+# 2. Send flow F to switch
+# 3. Send strict flow delete for F to switch
+# 4. Verify flow table in swtich
+# 6. Test PASSED iff all flows sent to switch in step 2 above,
+#    less flow removed in step 3 above, are returned in step 4 above;
+#    else test FAILED
+
+class Flow_Del_1(basic.SimpleProtocol):
+    """
+    Test FLOW_DEL_1 from draft top-half test plan
+    
+    INPUTS
+    None
+    """
+
+    def runTest(self):
+        fq_logger.debug("Flow_Del_1 TEST BEGIN")
+
+        # Clear all flows from switch
+
+        fq_logger.debug("Deleting all flows from switch")
+        rc = delete_all_flows(self.controller, fq_logger)
+        self.assertEqual(rc, 0, "Failed to delete all flows")
+
+        # Get switch capabilites
+
+        fq_logger.debug("Getting switch capabilities")        
+        sw = Switch()
+        sw.controller_set(self.controller)
+        self.assertTrue(sw.features_get(), "Get switch features failed")
+        self.assertTrue(sw.tbl_stats_get(), "Get table stats failed")
+
+        # Dream up some flow information, i.e. space to chose from for
+        # random flow parameter generation
+
+        fi = Flow_Info()
+        fi.rand(10)
+        
+        # Dream up a flow config
+
+        fc = Flow_Cfg()
+        fc.rand(fi, \
+                sw.tbl_stats.stats[0].wildcards, \
+                sw.sw_features.actions, \
+                sw.valid_ports \
+                )
+        fc = fc.canonical()
+
+        # Send it to the switch
+
+        fq_logger.debug("Sending flow add to switch:")
+        fq_logger.debug(str(fc))
+        ft = Flow_Tbl()
+        fc.send_rem = False
+        self.assertTrue(sw.flow_add(fc), "Failed to add flow")
+        ft.insert(fc)
+
+        # Dream up some different actions, with the same flow key
+
+        fc2 = copy.deepcopy(fc)
+        while True:
+            fc2.rand_mod(fi, \
+                         sw.sw_features.actions, \
+                         sw.valid_ports \
+                         )
+            if fc2 != fc:
+                break
+
+        # Delete strictly
+        
+        fq_logger.debug("Sending strict flow del to switch:")
+        fq_logger.debug(str(fc2))
+        self.assertTrue(sw.flow_del(fc2, True), "Failed to delete flow")
+        ft.delete(fc)
+
+        # Do barrier, to make sure all flows are in
+
+        self.assertTrue(sw.barrier(), "Barrier failed")
+
+        result = True
+
+        # Check for any error messages
+
+        if not sw.errors_verify(0):
+            result = False
+
+        # Verify flow table
+
+        sw.flow_tbl = ft
+        if not sw.flow_tbl_verify():
+            result = False
+
+        self.assertTrue(result, "Flow_Del_1 TEST FAILED")
+        fq_logger.debug("Flow_Del_1 TEST PASSED")
+
+
+# FLOW DELETE 2
+#
+# OVERVIEW
+# Loose delete of multiple flows
+#
+# PURPOSE
+# - Verify correct operation of loose delete of multiple flows
+#
+# PARAMETERS
+# Name: num_flows
+# Type: number
+# Description:
+# Number of flows to define
+# Default: 100
+#
+# PROCESS
+# 1. Delete all flows from switch
+# 2. Generate <num_flows> distinct flow configurations
+# 3. Send <num_flows> flow adds to switch, for flows generated in step 2 above
+# 4. Pick 1 defined flow F at random
+# 5. Repeatedly wildcard out qualifiers in match of F, creating F', such that
+#    F' will match more than 1 existing flow key
+# 6. Send loose flow delete for F' to switch
+# 7. Verify flow table in switch
+# 8. Test PASSED iff all flows sent to switch in step 3 above, less flows
+#    removed in step 6 above (i.e. those that match F'), are returned
+#    in step 7 above;
+#    else test FAILED
+
+class Flow_Del_2(basic.SimpleProtocol):
+    """
+    Test FLOW_DEL_2 from draft top-half test plan
+    
+    INPUTS
+    None
+    """
+
+    def runTest(self):
+        fq_logger.debug("Flow_Del_2 TEST BEGIN")
+
+        num_flows = test_param_get(fq_config, "num_flows", 100)
+
+        # Clear all flows from switch
+
+        fq_logger.debug("Deleting all flows from switch")
+        rc = delete_all_flows(self.controller, fq_logger)
+        self.assertEqual(rc, 0, "Failed to delete all flows")
+
+        # Get switch capabilites
+
+        fq_logger.debug("Getting switch capabilities")        
+        sw = Switch()
+        sw.controller_set(self.controller)
+        self.assertTrue(sw.features_get(), "Get switch features failed")
+        self.assertTrue(sw.tbl_stats_get(), "Get table stats failed")
+
+        # Dream up some flow information, i.e. space to chose from for
+        # random flow parameter generation
+
+        fi = Flow_Info()
+        # Shrunk, to increase chance of meta-matches
+        fi.rand(max(int(math.log(num_flows)) / 2, 1))
+        
+        # Dream up some flows
+
+        ft = Flow_Tbl()
+        ft.rand(sw, fi, num_flows)
+
+        # Send flow table to switch
+
+        fq_logger.debug("Sending flow adds to switch")
+        for fc in ft.values():          # Randomizes order of sending
+            fq_logger.debug("Adding flow:")
+            fq_logger.debug(str(fc));
+            self.assertTrue(sw.flow_add(fc), "Failed to add flow")
+
+        # Do barrier, to make sure all flows are in
+
+        self.assertTrue(sw.barrier(), "Barrier failed")
+
+        result = True
+
+        # Check for any error messages
+
+        if not sw.errors_verify(0):
+            result = False
+
+        # Verify flow table
+
+        sw.flow_tbl = ft
+        if not sw.flow_tbl_verify():
+            result = False
+
+        # Pick a random flow as a basis
+        
+        dfc = copy.deepcopy(ft.values()[0])
+        dfc.rand_mod(fi, sw.sw_features.actions, sw.valid_ports)
+
+        # Repeatedly wildcard qualifiers
+
+        for wi in shuffle(range(len(all_wildcards_list))):
+            w = all_wildcards_list[wi]
+            if w == ofp.OFPFW_NW_SRC_MASK or w == ofp.OFPFW_NW_DST_MASK:
+                n = wildcard_get(dfc.match.wildcards, w)
+                if n < 32:
+                    dfc.match.wildcards = wildcard_set(dfc.match.wildcards, \
+                                                       w, \
+                                                       random.randint(n + 1, 32) \
+                                                       )
+                else:
+                    continue
+            else:
+                if wildcard_get(dfc.match.wildcards, w) == 0:
+                    dfc.match.wildcards = wildcard_set(dfc.match.wildcards, w, 1)
+                else:
+                    continue
+            dfc = dfc.canonical()
+
+            # Count the number of flows that would be deleted
+
+            n = 0
+            for fc in ft.values():
+                if dfc.overlaps(fc, True):
+                    n = n + 1
+
+            # If more than 1, we found our loose delete flow spec
+            if n > 1:
+                break
+                    
+        fq_logger.debug("Deleting %d flows" % (n))
+        fq_logger.debug("Sending flow del to switch:")
+        fq_logger.debug(str(dfc))
+        self.assertTrue(sw.flow_del(dfc, False), "Failed to delete flows")
+
+        # Do barrier, to make sure all flows are in
+        self.assertTrue(sw.barrier(), "Barrier failed")
+
+        # Check for error message
+
+        if not sw.errors_verify(0):
+            result = False
+
+        # Apply flow mod to local flow table
+
+        for fc in ft.values():
+            if dfc.overlaps(fc, True):
+                ft.delete(fc)
+
+        # Verify flow table
+
+        sw.flow_tbl = ft
+        if not sw.flow_tbl_verify():
+            result = False
+
+        self.assertTrue(result, "Flow_Del_2 TEST FAILED")
+        fq_logger.debug("Flow_Del_2 TEST PASSED")
+
+
+# FLOW DELETE 4
+#
+# OVERVIEW
+# Flow removed messages
+#
+# PURPOSE
+# Verify that switch generates OFPT_FLOW_REMOVED/OFPRR_DELETE response
+# messages when deleting flows that were added with OFPFF_SEND_FLOW_REM flag
+#
+# PARAMETERS
+# None
+#
+# PROCESS
+# 1. Delete all flows from switch
+# 2. Send flow add to switch, with OFPFF_SEND_FLOW_REM set
+# 3. Send strict flow delete of flow to switch
+# 4. Verify that OFPT_FLOW_REMOVED/OFPRR_DELETE message is received from switch
+# 5. Verify flow table in switch
+# 6. Test PASSED iff all flows sent to switch in step 2 above, less flow
+#    removed in step 3 above, are returned in step 5 above, and that
+#    asynch message was received; else test FAILED
+
+
+class Flow_Del_4(basic.SimpleProtocol):
+    """
+    Test FLOW_DEL_4 from draft top-half test plan
+    
+    INPUTS
+    None
+    """
+
+    def runTest(self):
+        fq_logger.debug("Flow_Del_4 TEST BEGIN")
+
+        # Clear all flows from switch
+
+        fq_logger.debug("Deleting all flows from switch")
+        rc = delete_all_flows(self.controller, fq_logger)
+        self.assertEqual(rc, 0, "Failed to delete all flows")
+
+        # Get switch capabilites
+
+        fq_logger.debug("Getting switch capabilities")        
+        sw = Switch()
+        sw.controller_set(self.controller)
+        self.assertTrue(sw.features_get(), "Get switch features failed")
+        self.assertTrue(sw.tbl_stats_get(), "Get table stats failed")
+
+        # Dream up some flow information, i.e. space to chose from for
+        # random flow parameter generation
+
+        fi = Flow_Info()
+        fi.rand(10)
+        
+        # Dream up a flow config
+
+        fc = Flow_Cfg()
+        fc.rand(fi, \
+                sw.tbl_stats.stats[0].wildcards, \
+                sw.sw_features.actions, \
+                sw.valid_ports \
+                )
+        fc = fc.canonical()
+
+        # Send it to the switch. with "notify on removed"
+
+        fq_logger.debug("Sending flow add to switch:")
+        fq_logger.debug(str(fc))
+        ft = Flow_Tbl()
+        fc.send_rem = True
+        self.assertTrue(sw.flow_add(fc), "Failed to add flow")
+        ft.insert(fc)
+
+        # Dream up some different actions, with the same flow key
+
+        fc2 = copy.deepcopy(fc)
+        while True:
+            fc2.rand_mod(fi, \
+                         sw.sw_features.actions, \
+                         sw.valid_ports \
+                         )
+            if fc2 != fc:
+                break
+
+        # Delete strictly
+        
+        fq_logger.debug("Sending strict flow del to switch:")
+        fq_logger.debug(str(fc2))
+        self.assertTrue(sw.flow_del(fc2, True), "Failed to delete flow")
+        ft.delete(fc)
+
+        # Do barrier, to make sure all flows are in
+
+        self.assertTrue(sw.barrier(), "Barrier failed")
+
+        result = True
+
+        # Check for expected "removed" message
+
+        if not sw.errors_verify(0):
+            result = False
+
+        if not sw.removed_verify(1):
+            result = False
+
+        # Verify flow table
+
+        sw.flow_tbl = ft
+        if not sw.flow_tbl_verify():
+            result = False
+
+        self.assertTrue(result, "Flow_Del_4 TEST FAILED")
+        fq_logger.debug("Flow_Del_4 TEST PASSED")
+