Greatly changed:
- refactored, into many small tests, instead of 1 large on
  (follows published draft test plan
diff --git a/tests/flow_query.py b/tests/flow_query.py
index a09b886..8d29213 100644
--- a/tests/flow_query.py
+++ b/tests/flow_query.py
@@ -31,6 +31,10 @@
 #@var pa_config Local copy of global configuration data
 pa_config = None
 
+# For test priority
+test_prio = {}
+
+
 def test_set_init(config):
     """
     Set up function for packet action test classes
@@ -50,6 +54,10 @@
     pa_config = config
 
 
+def flip_coin():
+    return random.randint(1, 100) <= 50
+
+
 def shuffle(list):
     n = len(list)
     lim = n * n
@@ -80,7 +88,7 @@
     return random.randint(0, (1 << 32) - 1)
 
 
-class flow_info:
+class Flow_Info:
     # Members:
     # priorities - list of flow priorities
     # dl_addrs   - list of MAC addresses
@@ -120,11 +128,12 @@
             self.vlans.append(random.randint(1, 4094))
             i = i + 1
     
-        self.ethertypes = []
+        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
@@ -138,11 +147,12 @@
             self.ip_tos.append(random.randint(0, (1 << 8) - 1) & ~3)
             i = i + 1
     
-        self.ip_protos = []
+        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
@@ -178,19 +188,53 @@
 # TBD - These don't belong here
 
 all_wildcards_list = [ofp.OFPFW_IN_PORT,
-                      ofp.OFPFW_DL_VLAN,
-                      ofp.OFPFW_DL_SRC,
                       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_TP_SRC,
-                      ofp.OFPFW_TP_DST,
                       ofp.OFPFW_NW_SRC_MASK,
                       ofp.OFPFW_NW_DST_MASK,
-                      ofp.OFPFW_DL_VLAN_PCP,
-                      ofp.OFPFW_NW_TOS
+                      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,
@@ -210,6 +254,8 @@
     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, \
@@ -220,7 +266,7 @@
     return result
     
 
-class flow_cfg:
+class Flow_Cfg:
     # Members:
     # - match
     # - idle_timeout
@@ -236,113 +282,134 @@
         self.hard_timeout    = 0
         self.actions         = action_list.action_list()
 
-    def __eq__(self, x):
+    # {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 (self.match.wildcards & ofp.OFPFW_IN_PORT) == 0 \
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_IN_PORT) == 0 \
            and self.match.in_port != x.match.in_port:
             return False
-        if (self.match.wildcards & ofp.OFPFW_DL_SRC) == 0 \
-           and self.match.dl_src != x.match.dl_src:
-            return False
-        if (self.match.wildcards & ofp.OFPFW_DL_DST) == 0 \
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_DL_DST) == 0 \
            and self.match.dl_dst != x.match.dl_dst:
             return False
-        if (self.match.wildcards & ofp.OFPFW_DL_VLAN) == 0 \
+        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 (self.match.wildcards & ofp.OFPFW_DL_VLAN_PCP) == 0 \
+        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 (self.match.wildcards & ofp.OFPFW_DL_TYPE) == 0 \
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_DL_TYPE) == 0 \
            and self.match.dl_type != x.match.dl_type:
             return False
-        if (self.match.wildcards & ofp.OFPFW_NW_TOS) == 0 \
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_NW_TOS) == 0 \
            and self.match.nw_tos != x.match.nw_tos:
             return False
-        if (self.match.wildcards & ofp.OFPFW_NW_PROTO) == 0 \
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_NW_PROTO) == 0 \
            and self.match.nw_proto != x.match.nw_proto:
             return False
-        if (self.match.wildcards & ofp.OFPFW_NW_SRC_MASK) \
-               < ofp.OFPFW_NW_SRC_ALL:
-            m = ~((1 << ((self.match.wildcards & ofp.OFPFW_NW_SRC_MASK) \
-                         >> ofp.OFPFW_NW_SRC_SHIFT)) - 1)
+        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
-        if (self.match.wildcards & ofp.OFPFW_NW_DST_MASK) \
-               < ofp.OFPFW_NW_DST_ALL:
-            m = ~((1 << ((self.match.wildcards & ofp.OFPFW_NW_DST_MASK) \
-                         >> ofp.OFPFW_NW_DST_SHIFT)) - 1)
+        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 (self.match.wildcards & ofp.OFPFW_TP_SRC) == 0 \
-           and self.match.tp_src != x.match.tp_src:
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_TP_SRC) == 0 \
+               and self.match.tp_src != x.match.tp_src:
             return False
-        if (self.match.wildcards & ofp.OFPFW_TP_DST) == 0 \
-           and self.match.tp_dst != x.match.tp_dst:
+        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
-        return self.actions == x.actions  # N.B. Action lists are ordered
-
-    def __str__(self):
+        # Compare actions lists as unordered, since Argon may re-order action lists.
+        # This is in apparent violation of the spec.
+        # TBD - Verify, or create option in test to check ordered/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 == []
+        
+    def flow_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={"
+        result = result + (", wildcards=0x%x={" % (self.match.wildcards))
         sep = ""
-        for w in ofp.ofp_flow_wildcards_map:
-            if w == ofp.OFPFW_NW_SRC_SHIFT \
-               or w == ofp.OFPFW_NW_SRC_BITS \
-               or w == ofp.OFPFW_NW_SRC_ALL \
-               or w == ofp.OFPFW_NW_DST_SHIFT \
-               or w == ofp.OFPFW_NW_DST_BITS \
-               or w == ofp.OFPFW_NW_DST_ALL \
-               or w == ofp.OFPFW_ALL \
-               or self.match.wildcards & w == 0:
+        for w in all_wildcards_list:
+            if (self.match.wildcards & w) == 0:
                 continue
             if w == ofp.OFPFW_NW_SRC_MASK:
-                result = result + sep + "OFPFW_NW_SRC"
+                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:
-                result = result + sep + "OFPFW_NW_DST"
+                n = wildcard_get(self.match.wildcards, w)
+                if n > 0:
+                    result = result + sep + ("OFPFW_NW_DST(%d)" % (n))
             else:
-                result = result + sep + ofp.ofp_flow_wildcards_map[w]
+                result = result + sep + all_wildcard_names[w]
             sep = ", "
         result = result +"}"
-        if (self.match.wildcards & ofp.OFPFW_IN_PORT) == 0:
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_IN_PORT) == 0:
             result = result + (", in_port=%d" % (self.match.in_port))
-        if (self.match.wildcards & ofp.OFPFW_DL_SRC) == 0:
-            result = result + (", dl_src=%s" % (dl_addr_to_str(self.match.dl_src)))
-        if (self.match.wildcards & ofp.OFPFW_DL_DST) == 0:
+        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 (self.match.wildcards & ofp.OFPFW_DL_VLAN) == 0:
+        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 (self.match.wildcards & ofp.OFPFW_DL_VLAN_PCP) == 0:
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_DL_VLAN_PCP) == 0:
             result = result + (", dl_vlan_pcp=%d" % (self.match.dl_vlan_pcp))
-        if (self.match.wildcards & ofp.OFPFW_DL_TYPE) == 0:
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_DL_TYPE) == 0:
             result = result + (", dl_type=0x%x" % (self.match.dl_type))
-        if (self.match.wildcards & ofp.OFPFW_NW_TOS) == 0:
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_NW_TOS) == 0:
             result = result + (", nw_tos=0x%x" % (self.match.nw_tos))
-        if (self.match.wildcards & ofp.OFPFW_NW_PROTO) == 0:
+        if wildcard_get(self.match.wildcards, ofp.OFPFW_NW_PROTO) == 0:
             result = result + (", nw_proto=%d" % (self.match.nw_proto))
-        n = (self.match.wildcards & ofp.OFPFW_NW_SRC_MASK) >> ofp.OFPFW_NW_SRC_SHIFT
+        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, n)))
-        n = (self.match.wildcards & ofp.OFPFW_NW_DST_MASK) >> ofp.OFPFW_NW_DST_SHIFT
+            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, n)))
-        if (self.match.wildcards & ofp.OFPFW_TP_SRC) == 0:
+            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 (self.match.wildcards & ofp.OFPFW_TP_DST) == 0:
+        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.flow_key_str()
         result = result + (", idle_timeout=%d" % self.idle_timeout)
         result = result + (", hard_timeout=%d" % self.hard_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:
@@ -363,155 +430,9 @@
                 result = result + ("(port=%d,queue=%d)" % (a.port, a.queue_id))
         return result
 
-    def rand(self, fi, valid_wildcards, valid_actions, valid_ports):
-        # Start with no wildcards, i.e. everything specified
-        self.match.wildcards = 0
-        
-        # Make approx. 1% of flows exact
-        exact = True if random.randint(1, 100) == 1 else False
-
-        # 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 (ofp.OFPFW_IN_PORT & valid_wildcards) == 0 \
-           or exact \
-           or random.randint(1, 100) <= 50:
-            self.match.in_port = rand_pick(valid_ports)
-        else:
-            self.match.wildcards = self.match.wildcards | ofp.OFPFW_IN_PORT
-            
-        if (ofp.OFPFW_DL_DST & valid_wildcards) == 0 \
-           or exact \
-           or random.randint(1, 100) <= 50:
-            self.match.dl_dst = fi.rand_dl_addr()
-        else:
-            self.match.wildcards = self.match.wildcards | ofp.OFPFW_DL_DST
-
-        if (ofp.OFPFW_DL_SRC & valid_wildcards) == 0 \
-           or exact \
-           or random.randint(1, 100) <= 50:
-            self.match.dl_src = fi.rand_dl_addr()
-        else:
-            self.match.wildcards = self.match.wildcards | ofp.OFPFW_DL_SRC
-
-        if (ofp.OFPFW_DL_VLAN_PCP & valid_wildcards) == 0 \
-           or exact \
-           or random.randint(1, 100) <= 50:
-            self.match.dl_vlan_pcp = random.randint(0, (1 << 3) - 1)
-        else:
-            self.match.wildcards = self.match.wildcards | ofp.OFPFW_DL_VLAN_PCP
-
-        if (ofp.OFPFW_DL_VLAN & valid_wildcards) == 0 \
-           or exact \
-           or random.randint(1, 100) <= 50:
-            self.match.dl_vlan = fi.rand_vlan()
-        else:
-            self.match.wildcards = self.match.wildcards | ofp.OFPFW_DL_VLAN
-
-        if (ofp.OFPFW_DL_TYPE & valid_wildcards) == 0 \
-           or exact \
-           or random.randint(1, 100) <= 50:
-            self.match.dl_type = fi.rand_ethertype()
-        else:
-            self.match.wildcards = self.match.wildcards | ofp.OFPFW_DL_TYPE
-
-        if exact:
-            n = 0
-        else:
-            n = (valid_wildcards & ofp.OFPFW_NW_SRC_MASK) \
-                >> ofp.OFPFW_NW_SRC_SHIFT
-            if n > 32:
-                n = 32
-            n = random.randint(0, n)
-        self.match.wildcards = self.match.wildcards \
-                               | (n << ofp.OFPFW_NW_SRC_SHIFT)
-        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}
-            self.match.dl_type   = rand_pick([0x0800, 0x0806])
-            self.match.wildcards = self.match.wildcards & ~ofp.OFPFW_DL_TYPE
-
-        if exact:
-            n = 0
-        else:
-            n = (valid_wildcards & ofp.OFPFW_NW_DST_MASK) \
-                >> ofp.OFPFW_NW_DST_SHIFT
-            if n > 32:
-                n = 32
-            n = random.randint(0, n)
-        self.match.wildcards = self.match.wildcards \
-                               | (n << ofp.OFPFW_NW_DST_SHIFT)
-        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}
-            self.match.dl_type   = rand_pick([0x0800, 0x0806])
-            self.match.wildcards = self.match.wildcards & ~ofp.OFPFW_DL_TYPE
-
-        if (ofp.OFPFW_NW_TOS & valid_wildcards) == 0 \
-           or exact \
-           or random.randint(1, 100) <= 50:
-            self.match.nw_tos = fi.rand_ip_tos()
-            # Specifying a TOS value requires that Ethertype is IP
-            self.match.dl_type   = 0x0800
-            self.match.wildcards = self.match.wildcards & ~ofp.OFPFW_DL_TYPE
-        else:
-            self.match.wildcards = self.match.wildcards | ofp.OFPFW_NW_TOS
-
-        if (ofp.OFPFW_NW_PROTO & valid_wildcards) == 0 \
-           or exact \
-           or random.randint(1, 100) <= 50:
-            self.match.nw_proto = fi.rand_ip_proto()
-            # Specifying an IP protocol requires that Ethertype is IP
-            self.match.dl_type   = 0x0800
-            self.match.wildcards = self.match.wildcards & ~ofp.OFPFW_DL_TYPE
-        else:            
-            self.match.wildcards = self.match.wildcards | ofp.OFPFW_NW_PROTO
-            
-        if (ofp.OFPFW_TP_SRC & valid_wildcards) == 0 \
-           or exact\
-           or random.randint(1, 100) <= 50:
-            self.match.tp_src = fi.rand_l4_port()
-            # Specifying a L4 port requires that IP protcol is
-            # one of {ICMP, TCP, UDP}
-            self.match.nw_proto = rand_pick([1, 6, 17])
-            self.match.wildcards = self.match.wildcards & ~ofp.OFPFW_NW_PROTO
-            # Specifying a L4 port requirues that Ethertype is IP
-            self.match.dl_type   = 0x0800
-            self.match.wildcards = self.match.wildcards & ~ofp.OFPFW_DL_TYPE
-        else:
-            self.match.wildcards = self.match.wildcards | ofp.OFPFW_TP_SRC
-
-        if (ofp.OFPFW_TP_DST & valid_wildcards) == 0 \
-           or exact \
-           or random.randint(1, 100) <= 50:
-            self.match.tp_dst = fi.rand_l4_port()
-            # Specifying a L4 port requires that IP protcol is
-            # one of {ICMP, TCP, UDP}
-            self.match.nw_proto = rand_pick([1, 6, 17])
-            self.match.wildcards = self.match.wildcards & ~ofp.OFPFW_NW_PROTO
-            # Specifying a L4 port requirues that Ethertype is IP
-            self.match.dl_type   = 0x0800
-            self.match.wildcards = self.match.wildcards & ~ofp.OFPFW_DL_TYPE
-        else:
-            self.match.wildcards = self.match.wildcards | ofp.OFPFW_TP_DST
-
-        # 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
-
-        # 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()
+    # Randomize flow data for flow modifies
+    def rand_mod(self, fi, valid_actions, valid_ports):
+        self.cookie = random.randint(0, (1 << 53) - 1)
 
         # Action lists are ordered, so pick an ordered random subset of
         # supported actions
@@ -587,6 +508,200 @@
 
         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)
+
+# <TBD>
+# Due to a bug in OVS, don't specify nw_proto on it's own.
+# OVS will allow specifying a value for nw_proto, even if dl_type is not
+# specified as IP.
+# REMOVE FOR ARGON TESTING, AND BE SURE ABOUT INDENTATION
+# </TBD>
+#         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_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 and nw_dst 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)
+        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_proto, nw_tos, tp_src and tp_dst cannot be specified, must be wildcarded
+            result.match.wildcards = wildcard_set(result.match.wildcards, ofp.OFPFW_NW_PROTO, 1)
+            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)
+        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
@@ -594,84 +709,78 @@
     # "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 self.priority != x.priority:
-            return False
-        if (self.match.wildcards & ofp.OFPFW_IN_PORT) == 0:
-            if (x.match.wildcards & ofp.OFPFW_IN_PORT) == 0:
+        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 (self.match.wildcards & ofp.OFPFW_DL_VLAN) == 0:
-            if (x.match.wildcards & ofp.OFPFW_DL_VLAN) == 0:
+        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 (self.match.wildcards & ofp.OFPFW_DL_SRC) == 0:
-            if (x.match.wildcards & ofp.OFPFW_DL_SRC) == 0:
+        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 (self.match.wildcards & ofp.OFPFW_DL_DST) == 0:
-            if (x.match.wildcards & ofp.OFPFW_DL_DST) == 0:
+        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 (self.match.wildcards & ofp.OFPFW_DL_TYPE) == 0:
-            if (x.match.wildcards & ofp.OFPFW_DL_TYPE) == 0:
+        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 (self.match.wildcards & ofp.OFPFW_NW_PROTO) == 0:
-            if (x.match.wildcards & ofp.OFPFW_NW_PROTO) == 0:
+        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 (self.match.wildcards & ofp.OFPFW_TP_SRC) == 0:
-            if (x.match.wildcards & ofp.OFPFW_TP_SRC) == 0:
+        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 (self.match.wildcards & ofp.OFPFW_TP_DST) == 0:
-            if (x.match.wildcards & ofp.OFPFW_TP_DST) == 0:
+        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 = (self.match.wildcards & ofp.OFPFW_NW_SRC_MASK) \
-             >> ofp.OFPFW_NW_SRC_SHIFT
-        nb = (x.match.wildcards & ofp.OFPFW_NW_SRC_MASK) \
-             >> ofp.OFPFW_NW_SRC_SHIFT
+        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 = (self.match.wildcards & ofp.OFPFW_NW_DST_MASK) \
-             >> ofp.OFPFW_NW_DST_SHIFT
-        nb = (x.match.wildcards & ofp.OFPFW_NW_DST_MASK) \
-             >> ofp.OFPFW_NW_DST_SHIFT
+        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 bit not equal
-        if (self.match.wildcards & ofp.OFPFW_DL_VLAN_PCP) == 0:
-            if (x.match.wildcards & ofp.OFPFW_DL_VLAN_PCP) == 0:
+                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 (self.match.wildcards & ofp.OFPFW_NW_TOS) == 0:
-            if (x.match.wildcards & ofp.OFPFW_NW_TOS) == 0:
+        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:
@@ -680,6 +789,7 @@
 
     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
@@ -688,523 +798,1272 @@
 
     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 FlowQuery(basic.SimpleProtocol):
-    """
-    """
 
-    def do_barrier(self):
-        barrier = message.barrier_request()
-        (resp, pkt) = self.controller.transact(barrier, 5)
-        self.assertTrue(resp is not None,
-                        "Did not receive response to barrier request"
-                        )
-    
+class Flow_Tbl:
+    def clear(self):
+        self.dict = {}
 
-    def verify_flows(self,
-                     sw_features,
-                     tbl_flows,
-                     num_flows,
-                     overlapf,
-                     num_overlaps
-                     ):
-        result = True
-        
-        # Check number of flows reported in table stats
+    def __init__(self):
+        self.clear()
 
-        self.logger.debug("Verifying table stats reports correct number of")
-        self.logger.debug("  active flows")
+    def find(self, f):
+        return self.dict.get(f.flow_key_str(), None)
+
+    def insert(self, f):
+        self.dict[f.flow_key_str()] = f
+
+    def delete(self, f):
+        del self.dict[f.flow_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
+
+
+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 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)
+        return True
+
+    def tbl_stats_get(self):
+        # Get table stats
         request = message.table_stats_request()
-        (tbl_stats_after, pkt) = self.controller.transact(request, timeout=2)
-        self.assertTrue(tbl_stats_after is not None,
-                        "No reply to table_stats_request"
-                        )
+        (self.tbl_stats, pkt) = self.controller.transact(request, timeout=2)
+        return (self.tbl_stats is not None)
 
-        num_flows_reported = 0
-        for ts in tbl_stats_after.stats:
-            num_flows_reported = num_flows_reported + ts.active_count
-
-        num_flows_expected = num_flows
-        if overlapf:
-            num_flows_expected = num_flows_expected - num_overlaps
-
-        self.logger.debug("Number of flows reported = "
-                          + str(num_flows_reported)
-                          )
-        self.logger.debug("Numer of flows expected = "
-                          + str(num_flows_expected)
-                          )
-        if num_flows_reported != num_flows_expected:
-            self.logger.error("Incorrect number of flows returned by table stats")
-            result = False
-
-        # Retrieve all flows from switch
-
-        self.logger.debug("Retrieving all flows from switch")
-        stat_req = message.flow_stats_request()
+    def flow_stats_get(self):
+        request = message.flow_stats_request()
         query_match           = ofp.ofp_match()
         query_match.wildcards = ofp.OFPFW_ALL
-        stat_req.match    = query_match
-        stat_req.table_id = 0xff
-        stat_req.out_port = ofp.OFPP_NONE;
-        flow_stats, pkt = self.controller.transact(stat_req, timeout=2)
-        self.assertTrue(flow_stats is not None, "Get all flow stats failed")
+        request.match    = query_match
+        request.table_id = 0xff
+        request.out_port = ofp.OFPP_NONE;
+        (self.flow_stats, pkt) = self.controller.transact(request, timeout=2)
+        return (self.flow_stats is not None)
 
-        # Verify retrieved flows
-
-        self.logger.debug("Verifying retrieved flows")
-
-        self.assertEqual(flow_stats.type,
-                         ofp.OFPST_FLOW,
-                         "Unexpected type of response message"
-                         )
-
-        num_flows_reported = len(flow_stats.stats)
-
-        self.logger.debug("Number of flows reported = "
-                          + str(num_flows_reported)
-                          )
-        self.logger.debug("Numer of flows expected = "
-                          + str(num_flows_expected)
-                          )
-        if num_flows_reported != num_flows_expected:
-            self.logger.error("Incorrect number of flows returned by table stats")
-            result = False
-
-        for f in tbl_flows:
-            f.resp_matched = False
-
-        num_resp_flows_matched = 0
-        for flow_stat in flow_stats.stats:
-            flow_in = flow_cfg()
-            flow_in.from_flow_stat(flow_stat)
-
-            matched = False
-            for f in tbl_flows:
-                if f.deleted:
-                    continue
-                if not f.resp_matched \
-                   and (not overlapf or not f.overlap) \
-                   and f == flow_in:
-                    f.resp_matched = True
-                    num_resp_flows_matched = num_resp_flows_matched + 1
-                    matched = True
-                    break
-            if not matched:
-                self.logger.error("Response flow")
-                self.logger.error(str(flow_in))
-                self.logger.error("does not match any configured flow")
-                result = False
-
-        self.logger.debug("Number of flows matched in response = "
-                          + str(num_resp_flows_matched)
-                          )
-        self.logger.debug("Number of flows expected = "
-                          + str(num_flows_expected)
-                          )
-        if num_resp_flows_matched != num_flows_expected:
-            for f in tbl_flows:
-                if not f.resp_matched:
-                    self.logger.error("Configured flow")
-                    self.logger.error("tbl_idx=%d, flow_idx=%d, %s" % (f.tbl_idx, f.flow_idx, str(f)))
-                    self.logger.error("missing in flow response")
-                    result = False
-
-        self.assertTrue(result, "Flow verification failed")
-
-    def flow_add(self, flow, overlapf):
+    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_mod_msg.cookie      = random.randint(0, (1 << 53) - 1)
-        flow.to_flow_mod_msg(flow_mod_msg)
+        flow_cfg.to_flow_mod_msg(flow_mod_msg)
         if overlapf:
             flow_mod_msg.flags = flow_mod_msg.flags | ofp.OFPFF_CHECK_OVERLAP
-        self.logger.debug("Sending flow_mod(add) request to switch")
-        rv = self.controller.message_send(flow_mod_msg)
-        self.assertTrue(rv != -1, "Error installing flow mod")
+        if flow_cfg.send_rem:
+            flow_mod_msg.flags = flow_mod_msg.flags | ofp.OFPFF_SEND_FLOW_REM            
+        return (self.controller.message_send(flow_mod_msg) != -1)
 
-        # TBD - Don't poll for each error message
-        if flow.overlap:
-            self.logger.debug("Flow overlaps with tbl_idx=%d flow_idx=%d"
-                              % (flow.overlaps_with[0], flow.overlaps_with[1])
-                              )
-        else:
-            self.logger.debug("Flow does not overlap")
-        self.logger.debug("Checking for error response from switch")
-        (errmsg, pkt) = self.controller.poll(ofp.OFPT_ERROR, 1)
-        if errmsg is not None:
-            # Got ERROR message
-            self.logger.debug("Got ERROR message, type = "
-                              + str(errmsg.type)
-                              + ", code = "
-                              + str(errmsg.code)
-                              )
-            
-            if errmsg.type == ofp.OFPET_FLOW_MOD_FAILED \
-               and errmsg.code == ofp.OFPFMFC_OVERLAP:
-                # Got "overlap" ERROR message
-                self.logger.debug("ERROR is overlap")
-                
-                self.assertTrue(overlapf and flow.overlap,
-                                "Overlap not expected"
-                                )
-            else:
-                self.logger.debug("ERROR is not overlap")
-                self.assertTrue(False,
-                                "Unexpected error message")
-
-        else:
-            # Did not get ERROR message
-            self.logger.debug("No ERROR message received")
-            self.assertTrue(not (overlapf and flow.overlap),
-                            "Did not get expected OVERLAP"
-                            )
-
-
-    def flow_del(self, flow, strictf):
+    def flow_mod(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.command     = ofp.OFPFC_MODIFY_STRICT if strictf else ofp.OFPFC_MODIFY
         flow_mod_msg.buffer_id   = 0xffffffff
-        flow_mod_msg.cookie      = random.randint(0, (1 << 53) - 1)
-        # TBD - Needs to be a test variable
+        flow_cfg.to_flow_mod_msg(flow_mod_msg)
+        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.to_flow_mod_msg(flow_mod_msg)
-        rv = self.controller.message_send(flow_mod_msg)
-        self.assertTrue(rv != -1, "Error installing flow mod")
-        # TBD - Don't poll for each error message
-        (errmsg, pkt) = self.controller.poll(ofp.OFPT_ERROR, 1)
-        if errmsg is not None:
-            # Got ERROR message
-            self.logger.debug("Got ERROR message, type = "
-                              + str(errmsg.type)
-                              + ", code = "
-                              + str(errmsg.code)
+        flow_cfg.to_flow_mod_msg(flow_mod_msg)
+        return (self.controller.message_send(flow_mod_msg) != -1)
+
+    def barrier(self):
+        barrier = message.barrier_request()
+        (resp, pkt) = self.controller.transact(barrier, 5)
+        return (resp is not None)
+
+    def errors_verify(self, num, type = 0, code = 0):
+        pa_logger.debug("Expecting %d error messages" % (num))
+        if num > 0:
+            pa_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
+            pa_logger.debug("Got error message, type=%d, code=%d" \
+                              % (errmsg.type, errmsg.code) \
                               )
-            self.assertTrue(False,
-                            "Unexpected error message"
-                            )
+            if num == 0 or errmsg.type != type or errmsg.code != code:
+                pa_logger.debug("Unexpected error message")
+                result = False
+            n = n + 1
+        if n != num:
+            pa_logger.error("Received %d error messages" % (n))
+            result = False
+        return result
+
+    def flow_tbl_verify(self):
+        result = True
+    
+        # Verify flow count in switch
+        pa_logger.debug("Reading table stats")
+        pa_logger.debug("Expecting %d flows" % (self.flow_tbl.count()))
+        if not self.tbl_stats_get():
+            pa_logger.error("Get table stats failed")
+            return False
+        n = 0
+        for ts in self.tbl_stats.stats:
+            n = n + ts.active_count
+        pa_logger.debug("Table stats reported %d active flows" \
+                          % (n) \
+                          )
+        if n != self.flow_tbl.count():
+            pa_logger.error("Incorrect number of active flows reported")
+            result = False
+    
+        # Read flows from switch
+        pa_logger.debug("Retrieving flows from switch")
+        pa_logger.debug("Expecting %d flows" % (self.flow_tbl.count()))
+        if not self.flow_stats_get():
+            pa_logger.error("Get flow stats failed")
+            return False
+        pa_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():
+            pa_logger.error("Switch reported incorrect number of flows")
+            result = False
+    
+        pa_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)
+            pa_logger.debug("Received flow:")
+            pa_logger.debug(str(flow_in))
+            fc = self.flow_tbl.find(flow_in)
+            if fc is None:
+                pa_logger.error("does not match any defined flow")
+                result = False
+            elif fc.matched:
+                pa_logger.error("re-matches defined flow:")
+                pa_logger.debug(str(fc))
+                result = False
+            else:
+                pa_logger.debug("matched")
+                if not flow_in == fc:
+                    pa_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:
+                pa_logger.error("Defined flow:")
+                pa_logger.error(str(fc))
+                pa_logger.error("was not returned by switch")
+                result = False
+    
+        return result
 
 
-    # Add flows to capacity, make sure they can be read back, and delete them
+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 test1(self,
-              overlapf,                 # True <=> When sending flow adds to
-                                        # switch, include the "check for
-                                        # overlap" flag, and verify that an
-                                        # error message is received
-                                        # if an overlapping flow is defined
-              strictf                   # True <=> When deleting flows, delete
-                                        # them strictly
-              ):
-        """
-        """
+    def runTest(self):
+        pa_logger.debug("Flow_Add_5 TEST BEGIN")
 
+        num_flows = test_param_get(pa_config, "num_flows", 100)
+        
         # Clear all flows from switch
-        self.logger.debug("Deleting all flows from switch")
+
+        pa_logger.debug("Deleting all flows from switch")
         rc = delete_all_flows(self.controller, pa_logger)
         self.assertEqual(rc, 0, "Failed to delete all flows")
 
-        # Get valid port numbers
-        # Get number of tables supported
-        # Get actions supported by switch
+        # Get switch capabilites
 
-        self.logger.debug("Retrieving features from switch")
-        request = message.features_request()
-        (sw_features, pkt) = self.controller.transact(request, timeout=2)
-        self.assertTrue(sw_features is not None, "No reply to features_request")
-        self.logger.debug("Switch features -")
-        self.logger.debug("Number of tables: " + str(sw_features.n_tables))
-        self.logger.debug("Supported actions: " + hex(sw_features.actions))
-        self.logger.debug("Ports: "
-                          + str(map(lambda x: x.port_no, sw_features.ports))
-                          )
+        pa_logger.debug("Getting switch capabilities")        
+        sw = Switch()
+        sw.controller = self.controller
+        self.assertTrue(sw.features_get(), "Get switch features failed")
+        self.assertTrue(sw.tbl_stats_get(), "Get table stats failed")
 
-        # For each table, get wildcards supported maximum number of flows
+        if num_flows == 0:
+            # Number of flows requested was 0
+            # => Generate max number of flows
 
-        self.logger.debug("Retrieving table stats from switch")
-        request = message.table_stats_request()
-        (tbl_stats, pkt) = self.controller.transact(request, timeout=2)
-        self.assertTrue(tbl_stats is not None,
-                        "No reply to table_stats_request"
-                        )
-        active_count = 0
-        max_entries  = 0
-        tbl_idx = 0
-        while tbl_idx < sw_features.n_tables:
-            self.logger.debug("Table " + str(tbl_idx) + " - ")
-            self.logger.debug("Supported wildcards: "
-                              + hex(tbl_stats.stats[tbl_idx].wildcards)
-                              )
-            self.logger.debug("Max entries: "
-                              + str(tbl_stats.stats[tbl_idx].max_entries)
-                              )
-            self.logger.debug("Active count: "
-                              + str(tbl_stats.stats[tbl_idx].active_count)
-                              )
-            max_entries  = max_entries + tbl_stats.stats[tbl_idx].max_entries
-            active_count = active_count + tbl_stats.stats[tbl_idx].active_count
-            tbl_idx = tbl_idx + 1
+            for ts in sw.tbl_stats.stats:
+                num_flows = num_flows + ts.max_entries
 
-        self.logger.debug("Total active entries = "
-                          + str(active_count)
-                          )
-        self.assertEqual(active_count,
-                         0,
-                         "Delete all flows failed"
-                         )
-
-        # TBD - For testing only, since Open vSWitch reports
-        # ridiculously large capacity; remove
-        sw_features.n_tables = 1
-        tbl_stats.stats[0].max_entries = 10
-        max_entries = tbl_stats.stats[0].max_entries
+        pa_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()
-        n = int(math.log(max_entries))
-        if not overlapf:
-            # Generated space smaller when testing overlaps,
-            # to increase likelihood of some
-            n = 2 * n
-        fi.rand(n)
 
-        # For each table, think up flows to fill it
+        fi = Flow_Info()
+        fi.rand(2 * int(math.log(num_flows)))
 
-        self.logger.debug("Creating flows")
-        num_flows = 0
-        num_overlaps = 0
-        tbl_flows = []
-        tbl_idx = 0
+        # Create a flow table
 
-        while tbl_idx < sw_features.n_tables:
-            flow_idx = 0
-            while flow_idx < tbl_stats.stats[tbl_idx].max_entries:
-                flow_out = flow_cfg()
-                if overlapf and num_flows == 1:
-                    # Make 2nd flow a copy of the first,
-                    # to guarantee at least 1 overlap
-                    flow_out = copy.deepcopy(tbl_flows[0])
-                    flow_out.overlap = True
-                    flow_out.overlaps_with = [0, 0]
-                    num_overlaps = num_overlaps + 1
-                else:
-                    flow_out.rand(fi,
-                                  tbl_stats.stats[tbl_idx].wildcards,
-                                  sw_features.actions,
-                                  map(lambda x: x.port_no, sw_features.ports)
-                                  )
-                    flow_out.overlap  = False
-                    
-                    for f in tbl_flows:
-                        if (not overlapf or not f.overlap) \
-                           and flow_out.overlaps(f, False):
-                            flow_out.overlap = True
-                            flow_out.overlaps_with = [f.tbl_idx, f.flow_idx]
-                            num_overlaps = num_overlaps + 1
+        ft = Flow_Tbl()
+        ft.rand(sw, fi, num_flows)
 
-                flow_out.tbl_idx  = tbl_idx
-                flow_out.flow_idx = flow_idx
+        # Send flow table to switch
 
-                self.logger.debug("tbl_idx=%d, flow_idx=%d, %s" % (tbl_idx, flow_idx, str(flow_out)))
+        pa_logger.debug("Sending flow adds to switch")
+        for fc in ft.values():          # Randomizes order of sending
+            pa_logger.debug("Adding flow:")
+            pa_logger.debug(str(fc));
+            self.assertTrue(sw.flow_add(fc), "Failed to add flow")
 
-                tbl_flows.append(flow_out)
+        # Do barrier, to make sure all flows are in
 
-                num_flows = num_flows + 1
-                flow_idx = flow_idx + 1
-            tbl_idx = tbl_idx + 1
+        self.assertTrue(sw.barrier(), "Barrier failed")
 
-        self.logger.debug("Created " + str(num_flows)
-                          + " flows, with " + str(num_overlaps)
-                          + " overlaps"
-                          )
+        result = True
 
-        # Send all flows to switch
+        # Check for any error messages
 
-        self.logger.debug("Sending flows to switch")
-        for f in tbl_flows:
-            self.flow_add(f, overlapf)
-            f.deleted = False
+        if not sw.errors_verify(0):
+            result = False
 
-        # Send barrier, to make sure all flows are in
-        self.do_barrier()               
+        # Verify flow table
 
-        # Red back all flows from switch, and verify
+        sw.flow_tbl = ft
+        if not sw.flow_tbl_verify():
+            result = False
 
-        self.verify_flows(sw_features,
-                          tbl_flows,
-                          num_flows,
-                          overlapf,
-                          num_overlaps
-                          )
-
-        # Delete a flows from switch
-
-        if strictf:
-            # Strict delete
-            
-            # Delete a few flows, in random order, individually (i.e. strictly)
-            
-            del_flow_idxs = shuffle(range(len(tbl_flows)))
-            # TBD - Limited, for testing only; remove
-            del_flow_idxs = del_flow_idxs[0 : random.randint(3, 3)]
-            for di in del_flow_idxs:
-                f = tbl_flows[di]
-                tbl_idx  = f.tbl_idx
-                flow_idx = f.flow_idx
-                if (overlapf and f.overlap):
-                    self.logger.debug("Flow tbl_idx = " + str(tbl_idx)
-                                      + ", flow_idx = " + str(flow_idx)
-                                      + " was an overlap, skipping delete"
-                                      )
-                else:
-                    self.logger.debug("Deleting flow, tbl_idx = "
-                                      + str(tbl_idx) + ", flow_idx = "
-                                      + str(flow_idx)
-                                      )
-                    self.flow_del(f, True)
-                    f.deleted = True
-                    num_flows = num_flows - 1
-
-            # Send barrier, to make sure all flows are deleted
-            self.do_barrier();
-
-            # Red back all flows from switch, and verify
-
-            self.verify_flows(sw_features,
-                              tbl_flows,
-                              num_flows,
-                              overlapf,
-                              num_overlaps
-                              )
-
-            # Delete all remaining flows, in random order,
-            # individually (i.e. strictly)
-            
-            del_flow_idxs = shuffle(range(len(tbl_flows)))
-            for di in del_flow_idxs:
-                f = tbl_flows[di]
-                if f.deleted:
-                    continue
-                tbl_idx  = f.tbl_idx
-                flow_idx = f.flow_idx
-                if (overlapf and f.overlap):
-                    self.logger.debug("Flow tbl_idx = "
-                                      + str(tbl_idx)
-                                      + ", flow_idx = "
-                                      + str(flow_idx)
-                                      + " was an overlap, skipping delete"
-                                      )
-                else:
-                    self.logger.debug("Deleting flow, tbl_idx = "
-                                      + str(tbl_idx)
-                                      + ", flow_idx = "
-                                      + str(flow_idx)
-                                      )
-                    self.flow_del(f, True)
-                    f.deleted = True
-                    num_flows = num_flows - 1
-
-            # Send barrier, to make sure all flows are deleted
-            self.do_barrier()
-            
-            # Red back all flows from switch (i.e. none), and verify
-            
-            self.verify_flows(sw_features,
-                              tbl_flows,
-                              num_flows,
-                              overlapf,
-                              num_overlaps
-                              )
-
-        else:
-            # Non-strict delete
-
-            # Pick a flow at random that had at least 1 qualifier specified,
-            # wildcard a qualifier that was specified,
-            # and do a non-strict delete
-            # Keep wildcarding specified qualifiers, one by one, and deleteing,
-            # until everything is wildcarded,
-            # and hence all flows should be deleted
-
-            while True:
-                f = tbl_flows[random.randint(0, len(tbl_flows) - 1)]
-                if f.match.wildcards != tbl_stats.stats[f.tbl_idx].wildcards:
-                    self.logger.debug("Choosing flow for basis of non-strict delete")
-                    self.logger.debug("  tbl_idx=%d flow_idx=%d" % (f.tbl_idx, f.flow_idx))
-                    self.logger.debug("  " + str(f))
-                    break
-
-            # For each qualifier, in random order, if it was specified,
-            # wildcard it, do a delete, and check the results
-                
-            wildcard_idxs = shuffle(range(len(all_wildcards_list)))
-            for wi in wildcard_idxs:
-                w = all_wildcards_list[wi]
-                if (f.match.wildcards & w) != 0:
-                    continue
-
-                if w ==  ofp.OFPFW_NW_SRC_MASK:
-                    f.match.wildcards = (f.match.wildcards
-                                         & ~ofp.OFPFW_NW_SRC_MASK
-                                         ) \
-                                        | ofp.OFPFW_NW_SRC_ALL
-                    wn = "OFPFW_NW_SRC"
-                elif w ==  ofp.OFPFW_NW_DST_MASK:
-                    f.match.wildcards = (f.match.wildcards
-                                         & ~ofp.OFPFW_NW_DST_MASK
-                                         ) \
-                                        | ofp.OFPFW_NW_DST_ALL
-                    wn = "OFPFW_NW_DST"
-                else:
-                    f.match.wildcards = f.match.wildcards | w
-                    wn = ofp.ofp_flow_wildcards_map[w]
-
-                self.logger.debug("Adding wildcard %s" % (wn))
-                self.logger.debug(str(f))
-
-                # Mark all flows which would be deleted by this
-                # non-strict delete
-                
-                for ff in tbl_flows:
-                    if not ff.deleted and f.overlaps(ff, True):
-                        self.logger.debug("Deleting flow, tbl_idx = "
-                                          + str(ff.tbl_idx) + ", flow_idx = "
-                                          + str(ff.flow_idx)
-                                          )
-                        ff.deleted = True
-                        num_flows = num_flows - 1
-                        
-                self.flow_del(f, False)
-                
-                # Send barrier, to make sure all flows are deleted
-                self.do_barrier()
-                
-                # Red back all flows from switch, and verify
-                
-                self.verify_flows(sw_features,
-                                  tbl_flows,
-                                  num_flows,
-                                  overlapf,
-                                  num_overlaps
-                                  )
+        self.assertTrue(result, "Flow_Add_5 TEST FAILED")
+        pa_logger.debug("Flow_Add_5 TEST PASSED")
 
 
+class Flow_Add_5_1(basic.SimpleProtocol):
+    """
+    Test FLOW_ADD_5.1 from draft top-half test plan
+
+    INPUTS
+    None
+    """
+    
+    def runTest(self):
+        pa_logger.debug("Flow_Add_5_1 TEST BEGIN")
+
+        num_flows = test_param_get(pa_config, "num_flows", 100)
+        
+        # Clear all flows from switch
+
+        pa_logger.debug("Deleting all flows from switch")
+        rc = delete_all_flows(self.controller, pa_logger)
+        self.assertEqual(rc, 0, "Failed to delete all flows")
+
+        # Get switch capabilites
+
+        pa_logger.debug("Getting switch capabilities")        
+        sw = Switch()
+        sw.controller = 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 != fc:
+                break
+
+        ft = Flow_Tbl()
+        ft.insert(fcc)
+
+        # Send it to the switch
+
+        pa_logger.debug("Sending flow add to switch:")
+        pa_logger.debug(str(fc))
+        pa_logger.debug("should be canonicalized as:")
+        pa_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")
+        pa_logger.debug("Flow_Add_5_1 TEST PASSED")
+
+
+# Disable this test by default, since the flow capacity reported by OVS is bogus.
+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):
-        """
-        Run all tests
-        """
+        pa_logger.debug("Flow_Add_6 TEST BEGIN")
 
-        self.test1(False, True)         # Test with no overlaps, strict delete
-        self.test1(True,  True)         # Test with overlaps, strict delete
-#        self.test1(False, False)        # Test with no overlaps, non-strict delete
-#        self.test1(True,  False)        # Test with overlaps, non-strict delete
+        # Clear all flows from switch
 
+        pa_logger.debug("Deleting all flows from switch")
+        rc = delete_all_flows(self.controller, pa_logger)
+        self.assertEqual(rc, 0, "Failed to delete all flows")
+
+        # Get switch capabilites
+
+        pa_logger.debug("Getting switch capabilities")        
+        sw = Switch()
+        sw.controller = self.controller
+        self.assertTrue(sw.features_get(), "Get switch features failed")
+        self.assertTrue(sw.tbl_stats_get(), "Get table stats failed")
+
+        for ts in sw.tbl_stats.stats:
+            num_flows = num_flows + ts.max_entries
+
+        pa_logger.debug("Switch capacity is %d flows" % (num_flows))        
+        pa_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(2 * int(math.log(num_flows)))
+
+        # Create a flow table, to switch's capacity
+
+        ft = Flow_Tbl()
+        ft.rand(sw, fi, num_flows)
+
+        # Send flow table to switch
+
+        pa_logger.debug("Sending flow adds to switch")
+        for fc in ft.values():          # Randomizes order of sending
+            pa_logger.debug("Adding flow:")
+            pa_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
+
+        pa_logger.debug("Creating one more flow")
+        while True:
+            fc = Flow_Cfg()
+            fc.rand(fi, \
+                    sw.tbl_stats.stats[tbl].wildcards, \
+                    sw.sw_features.actions, \
+                    sw.valid_ports \
+                    )
+            fc = fc.canonical()
+            if ft.find(fc):
+                continue
+
+        # Send one-more flow
+
+        fc.send_rem = False
+        pa_logger.debug("Sending flow add switch")
+        pa_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")
+        pa_logger.debug("Flow_add_6 TEST PASSED")
+
+
+class Flow_Add_7(basic.SimpleProtocol):
+    """
+    Test FLOW_ADD_7 from draft top-half test plan
+    
+    INPUTS
+    None
+    """
+
+    def runTest(self):
+        pa_logger.debug("Flow_Add_7 TEST BEGIN")
+
+        # Clear all flows from switch
+
+        pa_logger.debug("Deleting all flows from switch")
+        rc = delete_all_flows(self.controller, pa_logger)
+        self.assertEqual(rc, 0, "Failed to delete all flows")
+
+        # Get switch capabilites
+
+        pa_logger.debug("Getting switch capabilities")        
+        sw = Switch()
+        sw.controller = 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
+
+        pa_logger.debug("Sending flow add to switch:")
+        pa_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
+        
+        pa_logger.debug("Sending flow add to switch:")
+        pa_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")
+        pa_logger.debug("Flow_Add_7 TEST PASSED")
+
+
+class Flow_Add_8(basic.SimpleProtocol):
+    """
+    Test FLOW_ADD_8 from draft top-half test plan
+    
+    INPUTS
+    None
+    """
+
+    def runTest(self):
+        pa_logger.debug("Flow_Add_8 TEST BEGIN")
+
+        # Clear all flows from switch
+
+        pa_logger.debug("Deleting all flows from switch")
+        rc = delete_all_flows(self.controller, pa_logger)
+        self.assertEqual(rc, 0, "Failed to delete all flows")
+
+        # Get switch capabilites
+
+        pa_logger.debug("Getting switch capabilities")        
+        sw = Switch()
+        sw.controller = 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
+
+        pa_logger.debug("Sending flow add to switch:")
+        pa_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]
+        pa_logger.debug("Wildcarding out %s" % (wn))
+        fc2.match.wildcards = fc2.match.wildcards | w
+
+        # Send that to the switch, with overlap checking
+        
+        pa_logger.debug("Sending flow add to switch:")
+        pa_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")
+        pa_logger.debug("Flow_Add_8 TEST PASSED")
+
+
+class Flow_Mod_1(basic.SimpleProtocol):
+    """
+    Test FLOW_MOD_1 from draft top-half test plan
+    
+    INPUTS
+    None
+    """
+
+    def runTest(self):
+        pa_logger.debug("Flow_Mod_1 TEST BEGIN")
+
+        # Clear all flows from switch
+
+        pa_logger.debug("Deleting all flows from switch")
+        rc = delete_all_flows(self.controller, pa_logger)
+        self.assertEqual(rc, 0, "Failed to delete all flows")
+
+        # Get switch capabilites
+
+        pa_logger.debug("Getting switch capabilities")        
+        sw = Switch()
+        sw.controller = 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
+
+        pa_logger.debug("Sending flow add to switch:")
+        pa_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
+        
+        pa_logger.debug("Sending strict flow mod to switch:")
+        pa_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")
+        pa_logger.debug("Flow_Mod_1 TEST PASSED")
+
+        
+class Flow_Mod_2(basic.SimpleProtocol):
+    """
+    Test FLOW_MOD_2 from draft top-half test plan
+    
+    INPUTS
+    None
+    """
+
+    def runTest(self):
+        pa_logger.debug("Flow_Mod_2 TEST BEGIN")
+
+        num_flows = test_param_get(pa_config, "num_flows", 100)
+
+        # Clear all flows from switch
+
+        pa_logger.debug("Deleting all flows from switch")
+        rc = delete_all_flows(self.controller, pa_logger)
+        self.assertEqual(rc, 0, "Failed to delete all flows")
+
+        # Get switch capabilites
+
+        pa_logger.debug("Getting switch capabilities")        
+        sw = Switch()
+        sw.controller = 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(int(math.log(num_flows)) / 2)  # Shrunk, to increase chance of meta-matches
+        
+        # Dream up some flows
+
+        ft = Flow_Tbl()
+        ft.rand(sw, fi, num_flows)
+
+        # Send flow table to switch
+
+        pa_logger.debug("Sending flow adds to switch")
+        for fc in ft.values():          # Randomizes order of sending
+            pa_logger.debug("Adding flow:")
+            pa_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
+                    
+        pa_logger.debug("Modifying %d flows" % (n))
+        pa_logger.debug("Sending flow mod to switch:")
+        pa_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.idle_timeout = mfc.idle_timeout
+                fc.hard_timeout = mfc.hard_timeout
+                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")
+        pa_logger.debug("Flow_Mod_2 TEST PASSED")
+
+
+class Flow_Mod_3(basic.SimpleProtocol):
+    """
+    Test FLOW_MOD_3 from draft top-half test plan
+    
+    INPUTS
+    None
+    """
+
+    def runTest(self):
+        pa_logger.debug("Flow_Mod_3 TEST BEGIN")
+
+        # Clear all flows from switch
+
+        pa_logger.debug("Deleting all flows from switch")
+        rc = delete_all_flows(self.controller, pa_logger)
+        self.assertEqual(rc, 0, "Failed to delete all flows")
+
+        # Get switch capabilites
+
+        pa_logger.debug("Getting switch capabilities")        
+        sw = Switch()
+        sw.controller = 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
+
+        pa_logger.debug("Sending flow mod to switch:")
+        pa_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")
+        pa_logger.debug("Flow_Mod_3 TEST PASSED")
+
+
+class Flow_Del_1(basic.SimpleProtocol):
+    """
+    Test FLOW_DEL_1 from draft top-half test plan
+    
+    INPUTS
+    None
+    """
+
+    def runTest(self):
+        pa_logger.debug("Flow_Del_1 TEST BEGIN")
+
+        # Clear all flows from switch
+
+        pa_logger.debug("Deleting all flows from switch")
+        rc = delete_all_flows(self.controller, pa_logger)
+        self.assertEqual(rc, 0, "Failed to delete all flows")
+
+        # Get switch capabilites
+
+        pa_logger.debug("Getting switch capabilities")        
+        sw = Switch()
+        sw.controller = 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
+
+        pa_logger.debug("Sending flow add to switch:")
+        pa_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
+        
+        pa_logger.debug("Sending strict flow del to switch:")
+        pa_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")
+        pa_logger.debug("Flow_Del_1 TEST PASSED")
+
+
+class Flow_Del_2(basic.SimpleProtocol):
+    """
+    Test FLOW_DEL_2 from draft top-half test plan
+    
+    INPUTS
+    None
+    """
+
+    def runTest(self):
+        pa_logger.debug("Flow_Del_2 TEST BEGIN")
+
+        num_flows = test_param_get(pa_config, "num_flows", 100)
+
+        # Clear all flows from switch
+
+        pa_logger.debug("Deleting all flows from switch")
+        rc = delete_all_flows(self.controller, pa_logger)
+        self.assertEqual(rc, 0, "Failed to delete all flows")
+
+        # Get switch capabilites
+
+        pa_logger.debug("Getting switch capabilities")        
+        sw = Switch()
+        sw.controller = 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(int(math.log(num_flows)) / 2)  # Shrunk, to increase chance of meta-matches
+        
+        # Dream up some flows
+
+        ft = Flow_Tbl()
+        ft.rand(sw, fi, num_flows)
+
+        # Send flow table to switch
+
+        pa_logger.debug("Sending flow adds to switch")
+        for fc in ft.values():          # Randomizes order of sending
+            pa_logger.debug("Adding flow:")
+            pa_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
+                    
+        pa_logger.debug("Deleting %d flows" % (n))
+        pa_logger.debug("Sending flow del to switch:")
+        pa_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")
+        pa_logger.debug("Flow_Del_2 TEST PASSED")
+
+
+# Disable this test by default, there seems to be an issue with error message reporting
+test_prio["Flow_Del_4"] = -1
+
+class Flow_Del_4(basic.SimpleProtocol):
+    """
+    Test FLOW_DEL_4 from draft top-half test plan
+    
+    INPUTS
+    None
+    """
+
+    def runTest(self):
+        pa_logger.debug("Flow_Del_4 TEST BEGIN")
+
+        # Clear all flows from switch
+
+        pa_logger.debug("Deleting all flows from switch")
+        rc = delete_all_flows(self.controller, pa_logger)
+        self.assertEqual(rc, 0, "Failed to delete all flows")
+
+        # Get switch capabilites
+
+        pa_logger.debug("Getting switch capabilities")        
+        sw = Switch()
+        sw.controller = 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"
+
+        pa_logger.debug("Sending flow add to switch:")
+        pa_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
+        
+        pa_logger.debug("Sending strict flow del to switch:")
+        pa_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(1, \
+                                ofp.OFPT_FLOW_REMOVED, \
+                                ofp.OFPRR_DELETE \
+                                ):
+            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")
+        pa_logger.debug("Flow_Del_4 TEST PASSED")
+