testutils: add verify_packet_in and verify_no_packet_in

Checking that the controller received a packet-in message is surprisingly
complicated to do correctly. Because the switch may send unexpected packet-ins
(Linux IP stack on IVS's OFPP_LOCAL, for example) each test needs logic to
match the packet data, in_port, etc to figure out if this is the packet-in
message it's looking for.

This change adds two testutils functions that automate this, and replaces all
the other implementations in tests.
diff --git a/src/python/oftest/testutils.py b/src/python/oftest/testutils.py
index 6609fc6..40b24ed 100644
--- a/src/python/oftest/testutils.py
+++ b/src/python/oftest/testutils.py
@@ -17,6 +17,7 @@
 import oftest.controller
 import oftest.dataplane
 import oftest.parse
+import oftest.ofutils
 import ofp
 
 global skipped_test_count
@@ -1307,4 +1308,102 @@
         test.assertTrue(byte_diff >= bytes and byte_diff <= bytes*1.1,
                         "Queue byte counter not updated properly (expected increase of %d, got increase of %d)" % (bytes, byte_diff))
 
+def packet_in_match(msg, data, in_port=None, reason=None):
+    """
+    Check whether the packet_in message 'msg' has fields matching 'data',
+    'in_port', and 'reason'.
+
+    This function handles truncated packet_in data. The 'in_port' and 'reason'
+    parameters are optional.
+
+    @param msg packet_in message
+    @param data Expected packet_in data
+    @param in_port Expected packet_in in_port, or None
+    @param reason Expected packet_in reason, or None
+    """
+
+    if in_port and in_port != msg.in_port:
+        logging.debug("Incorrect packet_in in_port (expected %d, received %d)", in_port, msg.in_port)
+        return False
+
+    if reason and reason != msg.reason:
+        logging.debug("Incorrect packet_in reason (expected %d, received %d)", reason, msg.reason)
+        return False
+
+    # Check that one of the packets is a prefix of the other.
+    # The received packet may be either truncated or padded, but not both.
+    # (Some of the padding may be truncated but that's irrelevant). We
+    # need to check that the smaller packet is a prefix of the larger one.
+    # Note that this check succeeds if the switch sends a zero-length
+    # packet-in.
+    compare_len = min(len(msg.data), len(data))
+    if data[:compare_len] != msg.data[:compare_len]:
+        logging.debug("Incorrect packet_in data")
+        logging.debug("Expected %s" % format_packet(data[:compare_len]))
+        logging.debug("Received %s" % format_packet(msg.data[:compare_len]))
+        return False
+
+    return True
+
+def verify_packet_in(test, data, in_port, reason, controller=None):
+    """
+    Assert that the controller receives a packet_in message matching data 'data'
+    from port 'in_port' with reason 'reason'. Does not trigger the packet_in
+    itself, that's up to the test case.
+
+    @param test Instance of base_tests.SimpleProtocol
+    @param pkt String to expect as the packet_in data
+    @param in_port OpenFlow port number to expect as the packet_in in_port
+    @param reason One of OFPR_* to expect as the packet_in reason
+    @param controller Controller instance, defaults to test.controller
+    @returns The received packet-in message
+    """
+
+    if controller == None:
+        controller = test.controller
+
+    end_time = time.time() + oftest.ofutils.default_timeout
+
+    while True:
+        msg, _ = controller.poll(ofp.OFPT_PACKET_IN, end_time - time.time())
+        if not msg:
+            # Timeout
+            break
+        elif packet_in_match(msg, data, in_port, reason):
+            # Found a matching message
+            break
+
+    test.assertTrue(msg is not None, 'Packet in message not received on port %d' % in_port)
+    return msg
+
+def verify_no_packet_in(test, data, in_port, controller=None):
+    """
+    Assert that the controller does not receive a packet_in message matching
+    data 'data' from port 'in_port'.
+
+    @param test Instance of base_tests.SimpleProtocol
+    @param pkt String to expect as the packet_in data
+    @param in_port OpenFlow port number to expect as the packet_in in_port
+    @param controller Controller instance, defaults to test.controller
+    """
+
+    if controller == None:
+        controller = test.controller
+
+    # Negative test, need to wait a short amount of time before checking we
+    # didn't receive the message.
+    time.sleep(0.5)
+
+    # Check every packet_in queued in the controller
+    while True:
+        msg, _ = controller.poll(ofp.OFPT_PACKET_IN, timeout=0)
+        if msg == None:
+            # No more queued packet_in messages
+            break
+        elif packet_in_match(msg, data, in_port, None):
+            # Found a matching message
+            break
+
+    test.assertTrue(msg == None, "Did not expect a packet-in message on port %d" % in_port)
+
 __all__ = list(set(locals()) - _import_blacklist)