add bsn_flow_idle extension

Similar to standard OpenFlow idle timeouts with a flow-removed notification,
but does not remove the flow.
diff --git a/openflow_input/bsn_flow_idle b/openflow_input/bsn_flow_idle
new file mode 100644
index 0000000..40a95d8
--- /dev/null
+++ b/openflow_input/bsn_flow_idle
@@ -0,0 +1,99 @@
+// Copyright 2013, Big Switch Networks, Inc.
+//
+// LoxiGen is licensed under the Eclipse Public License,
+// version 1.0 (EPL), with the following special exception:
+//
+// LOXI Exception
+//
+// As a special exception to the terms of the EPL, you may
+// distribute libraries generated by LoxiGen (LoxiGen Libraries)
+// under the terms of your choice, provided that copyright and
+// licensing notices generated by LoxiGen are not altered or removed
+// from the LoxiGen Libraries and the notice provided below is (i)
+// included in the LoxiGen Libraries, if distributed in source code
+// form and (ii) included in any documentation for the LoxiGen
+// Libraries, if distributed in binary form.
+//
+// Notice: "Copyright 2013, Big Switch Networks, Inc.
+// This library was generated by the LoxiGen Compiler."
+//
+// You may not use this file except in compliance with the EPL or
+// LOXI Exception. You may obtain a copy of the EPL at:
+//
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an "AS
+// IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+// express or implied. See the EPL for the specific language
+// governing permissions and limitations under the EPL.
+
+#version 4
+
+/*
+ * Notification of idle flows
+ *
+ * This extension allows the controller to request to be notified periodically
+ * about idle flows. It is very similar to the flow_removed message in standard
+ * OpenFlow, but does not delete the idle flows.
+ *
+ * If the extension is enabled using of_bsn_flow_idle_enable_set_request and
+ * the OFPFF_BSN_SEND_IDLE bit is set in the flow-mod, then the idle_timeout
+ * field in the flow-mod is not used for standard flow expiration. Instead,
+ * the switch will send an of_bsn_flow_idle message every idle_timeout seconds
+ * if the flow was not used during that period.
+ */
+
+struct of_bsn_flow_idle_enable_set_request : of_bsn_header {
+    uint8_t version;
+    uint8_t type == 4;
+    uint16_t length;
+    uint32_t xid;
+    uint32_t experimenter == 0x5c16c7;
+    uint32_t subtype == 36;
+    uint32_t enable;        // 0 to disable the extension, 1 to enable it
+};
+
+struct of_bsn_flow_idle_enable_set_reply : of_bsn_header {
+    uint8_t version;
+    uint8_t type == 4;
+    uint16_t length;
+    uint32_t xid;
+    uint32_t experimenter == 0x5c16c7;
+    uint32_t subtype == 37;
+    uint32_t enable;        // Resulting state, 0 disabled, 1 enabled
+    uint32_t status;        // Result code: 0 success
+};
+
+struct of_bsn_flow_idle_enable_get_request : of_bsn_header {
+    uint8_t version;
+    uint8_t type == 4;
+    uint16_t length;
+    uint32_t xid;
+    uint32_t experimenter == 0x5c16c7;
+    uint32_t subtype == 38;
+};
+
+struct of_bsn_flow_idle_enable_get_reply : of_bsn_header {
+    uint8_t version;
+    uint8_t type == 4;
+    uint16_t length;
+    uint32_t xid;
+    uint32_t experimenter == 0x5c16c7;
+    uint32_t subtype == 39;
+    uint32_t enabled;       // 0 if feature is disabled; 1 if feature enabled
+};
+
+struct of_bsn_flow_idle : of_bsn_header {
+    uint8_t version;
+    uint8_t type == 4;
+    uint16_t length;
+    uint32_t xid;
+    uint32_t experimenter == 0x5c16c7;
+    uint32_t subtype == 40;
+    uint64_t cookie;
+    uint16_t priority;
+    uint8_t table_id;
+    pad(5); // align to 8 bytes
+    of_match_t match;
+};
diff --git a/openflow_input/standard-1.3 b/openflow_input/standard-1.3
index 767594a..cbab686 100644
--- a/openflow_input/standard-1.3
+++ b/openflow_input/standard-1.3
@@ -250,6 +250,10 @@
     OFPFF_RESET_COUNTS = 0x4,
     OFPFF_NO_PKT_COUNTS = 0x8,
     OFPFF_NO_BYT_COUNTS = 0x10,
+
+    /* Non-standard, enabled by an experimenter message */
+    /* See the bsn_flow_idle input file */
+    OFPFF_BSN_SEND_IDLE = 0x80,
 };
 
 enum ofp_group(wire_type=uint32_t, complete=False) {
diff --git a/test_data/of13/bsn_flow_idle.data b/test_data/of13/bsn_flow_idle.data
new file mode 100644
index 0000000..61b656f
--- /dev/null
+++ b/test_data/of13/bsn_flow_idle.data
@@ -0,0 +1,54 @@
+-- binary
+04 04 # version, type
+00 38 # length
+12 34 56 78 # xid
+00 5c 16 c7 # experimenter
+00 00 00 28 # subtype
+fe dc ba 98 76 54 32 10 # cookie
+42 68 # priority
+14 # table_id
+00 # pad
+00 00 00 00 # pad
+00 01 # match.type
+00 16 # match.length
+80 00 01 08 # match.oxm_list[0].type_len - IN_PORT
+00 00 00 04 # match.oxm_list[0].value
+00 00 00 05 # match.oxm_list[0].mask
+80 00 2A 02 # match.oxm_list[1].type_len - ARP_OP
+00 01 # match.oxm_list[1].value
+00 00 # match.pad
+-- python
+ofp.message.bsn_flow_idle(
+    xid=0x12345678,
+    cookie=0xFEDCBA9876543210,
+    priority=17000,
+    table_id=20,
+    match=ofp.match([
+        ofp.oxm.in_port_masked(value=4, value_mask=5),
+        ofp.oxm.arp_op(value=1),
+    ]))
+-- c
+obj = of_bsn_flow_idle_new(OF_VERSION_1_3);
+of_bsn_flow_idle_xid_set(obj, 0x12345678);
+of_bsn_flow_idle_cookie_set(obj, 0xFEDCBA9876543210);
+of_bsn_flow_idle_priority_set(obj, 17000);
+of_bsn_flow_idle_table_id_set(obj, 20);
+{
+    of_match_t match = { OF_VERSION_1_3 };
+    match.fields.in_port = 4;
+    match.masks.in_port = 5;
+    match.fields.arp_op = 1;
+    OF_MATCH_MASK_ARP_OP_EXACT_SET(&match);
+    of_bsn_flow_idle_match_set(obj, &match);
+}
+-- java
+builder.setXid(0x12345678)
+    .setCookie(U64.parseHex("FEDCBA9876543210"))
+    .setPriority(17000)
+    .setTableId(TableId.of(20))
+    .setMatch(
+        factory.buildMatch()
+            .setMasked(MatchField.IN_PORT, OFPort.of(4), OFPort.of(5))
+            .setExact(MatchField.ARP_OP, ArpOpcode.of(1))
+                .build()
+    );