loci: generate validator class hierachies

Includes object length checks. Does not validate lists yet.
diff --git a/c_gen/templates/loci_validator.c b/c_gen/templates/loci_validator.c
index 8f9eeeb..3f80982 100644
--- a/c_gen/templates/loci_validator.c
+++ b/c_gen/templates/loci_validator.c
@@ -27,6 +27,7 @@
 ::
 :: include('_copyright.c')
 :: import loxi_globals
+:: from loxi_ir import *
 
 /**
  *
@@ -42,6 +43,61 @@
 
 #define VALIDATOR_LOG(...) LOCI_LOG_ERROR("Validator Error: " __VA_ARGS__)
 
+:: validator_name = lambda ofclass: "loci_validate_%s_%s" % (ofclass.name, ofclass.protocol.version.constant_version(prefix='OF_VERSION_'))
+
+/* Forward declarations */
+:: for version, proto in loxi_globals.ir.items():
+:: for ofclass in proto.classes:
+static int __attribute__((unused)) ${validator_name(ofclass)}(uint8_t *data, int len);
+:: #endfor
+:: #endfor
+
+:: readers = { 1: 'buf_u8_get', 2: 'buf_u16_get', 4: 'buf_u32_get' }
+:: types = { 1: 'uint8_t', 2: 'uint16_t', 4: 'uint32_t' }
+
+:: for version, proto in loxi_globals.ir.items():
+:: for ofclass in proto.classes:
+static int
+${validator_name(ofclass)}(uint8_t *data, int len)
+{
+    if (len < ${ofclass.base_length}) {
+        return -1;
+    }
+
+:: # Read and validate length fields
+:: for m in ofclass.members:
+:: if type(m) == OFLengthMember:
+    ${types[m.length]} wire_len;
+    ${readers[m.length]}(data + ${m.offset}, &wire_len);
+    if (wire_len > len || wire_len < ${ofclass.base_length}) {
+        return -1;
+    }
+
+    len = wire_len;
+:: #endif
+:: #endfor
+
+:: # Dispatch to subclass validators
+:: if ofclass.virtual:
+:: discriminator = ofclass.discriminator
+    ${types[discriminator.length]} wire_type;
+    ${readers[discriminator.length]}(data + ${discriminator.offset}, &wire_type);
+    switch (wire_type) {
+:: for subclass in proto.classes:
+:: if subclass.superclass == ofclass:
+    case ${subclass.member_by_name(discriminator.name).value}:
+        return ${validator_name(subclass)}(data, len);
+:: #endif
+:: #endfor
+    }
+:: #endif
+
+    return 0;
+}
+
+:: #endfor
+:: #endfor
+
 int
 of_validate_message(of_message_t msg, int len)
 {
@@ -57,7 +113,7 @@
     switch (version) {
 :: for version, proto in loxi_globals.ir.items():
     case ${version.constant_version(prefix='OF_VERSION_')}:
-        return 0;
+        return ${validator_name(proto.class_by_name('of_header'))}(msg, len);
 :: #endfor
     default:
         VALIDATOR_LOG("Bad version %d", OF_VERSION_1_3);
diff --git a/c_gen/templates/locitest/test_validator.c b/c_gen/templates/locitest/test_validator.c
index 858ec4f..81beb5d 100644
--- a/c_gen/templates/locitest/test_validator.c
+++ b/c_gen/templates/locitest/test_validator.c
@@ -49,10 +49,8 @@
 
     TEST_ASSERT(of_validate_message(msg, of_message_length_get(msg)) == 0);
 
-#if 0
     of_message_length_set(msg, of_message_length_get(msg) - 1);
     TEST_ASSERT(of_validate_message(msg, of_message_length_get(msg)) == -1);
-#endif
 
     of_table_stats_request_delete(obj);
     return TEST_PASS;