Refactor fuzztest
diff --git a/tests/fuzztest/SConscript b/tests/fuzztest/SConscript
index 02529b4..f07b5b2 100644
--- a/tests/fuzztest/SConscript
+++ b/tests/fuzztest/SConscript
@@ -31,6 +31,8 @@
 p2 = env.NanopbProto(["alltypes_proto3_static", "alltypes_proto3_static.options"])
 
 fuzz = malloc_env.Program(["fuzztest.c",
+                    "random_data.c",
+                    "validation.c",
                     "alltypes_pointer.pb.c",
                     "alltypes_static.pb.c",
                     "alltypes_proto3_pointer.pb.c",
@@ -49,7 +51,9 @@
 env.RunTest(fuzz, ARGS = [str(seed), str(iterations)])
 
 generate_message = malloc_env.Program(["generate_message.c",
+                    "random_data.c",
                     "alltypes_static.pb.c",
                     "$COMMON/pb_encode.o",
-                    "$COMMON/pb_common.o"])
+                    "$COMMON/pb_common.o",
+                    "$COMMON/malloc_wrappers.o"])
 
diff --git a/tests/fuzztest/fuzztest.c b/tests/fuzztest/fuzztest.c
index 7bdb5c9..20018a6 100644
--- a/tests/fuzztest/fuzztest.c
+++ b/tests/fuzztest/fuzztest.c
@@ -1,5 +1,10 @@
 /* Fuzz testing for the nanopb core.
  * Attempts to verify all the properties defined in the security model document.
+ *
+ * This program can run in three configurations:
+ * - Standalone fuzzer, generating its own inputs and testing against them.
+ * - Fuzzing target, reading input on stdin.
+ * - LLVM libFuzzer target, taking input as a function argument.
  */
 
 #include <pb_decode.h>
@@ -9,6 +14,8 @@
 #include <string.h>
 #include <assert.h>
 #include <malloc_wrappers.h>
+#include "random_data.h"
+#include "validation.h"
 #include "test_helpers.h"
 #include "alltypes_static.pb.h"
 #include "alltypes_pointer.pb.h"
@@ -16,199 +23,156 @@
 #include "alltypes_proto3_pointer.pb.h"
 
 /* Longer buffer size allows hitting more branches, but lowers performance. */
-#ifdef __AVR__
+#if defined(LLVMFUZZER)
+static size_t g_bufsize = 256*1024;
+#elif defined(__AVR__)
 static size_t g_bufsize = 2048;
 #else
 static size_t g_bufsize = 4096;
 #endif
 
+static bool do_decode(const uint8_t *buffer, size_t msglen, size_t structsize, const pb_msgdesc_t *msgtype, bool assert_success)
+{
+    bool status;
+    pb_istream_t stream;
+    size_t initial_alloc_count = get_alloc_count();
+    void *msg = malloc_with_check(structsize);
+    
+    memset(msg, 0, structsize);
+    stream = pb_istream_from_buffer(buffer, msglen);
+    status = pb_decode(&stream, msgtype, msg);
+
+    if (status)
+    {
+        validate_message(msg, structsize, msgtype);
+    }
+
+    if (assert_success)
+    {
+        if (!status) fprintf(stderr, "pb_decode: %s\n", PB_GET_ERROR(&stream));
+        assert(status);
+    }
+
+    pb_release(msgtype, msg);
+    free_with_check(msg);
+    assert(get_alloc_count() == initial_alloc_count);
+    
+    return status;
+}
+
+/* Do a decode -> encode -> decode -> encode roundtrip */
+static void do_roundtrip(const uint8_t *buffer, size_t msglen, size_t structsize, const pb_msgdesc_t *msgtype)
+{
+    bool status;
+    uint8_t *buf2 = malloc_with_check(g_bufsize);
+    uint8_t *buf3 = malloc_with_check(g_bufsize);
+    size_t msglen2, msglen3;
+    void *msg = malloc_with_check(structsize);
+    
+    /* Decode and encode the input data.
+     * This will bring it into canonical format.
+     */
+    {
+        pb_istream_t stream = pb_istream_from_buffer(buffer, msglen);
+        memset(msg, 0, structsize);
+        status = pb_decode(&stream, msgtype, msg);
+        if (!status) fprintf(stderr, "pb_decode: %s\n", PB_GET_ERROR(&stream));
+        assert(status);
+
+        validate_message(msg, structsize, msgtype);
+    }
+    
+    {
+        pb_ostream_t stream = pb_ostream_from_buffer(buf2, g_bufsize);
+        status = pb_encode(&stream, msgtype, msg);
+        if (!status) fprintf(stderr, "pb_encode: %s\n", PB_GET_ERROR(&stream));
+        assert(status);
+        msglen2 = stream.bytes_written;
+    }
+    
+    pb_release(msgtype, msg);
+
+    /* Then decode and encode again. Result should remain the same. */
+    {
+        pb_istream_t stream = pb_istream_from_buffer(buf2, msglen2);
+        memset(msg, 0, structsize);
+        status = pb_decode(&stream, msgtype, msg);
+        if (!status) fprintf(stderr, "pb_decode: %s\n", PB_GET_ERROR(&stream));
+        assert(status);
+
+        validate_message(msg, structsize, msgtype);
+    }
+    
+    {
+        pb_ostream_t stream = pb_ostream_from_buffer(buf3, g_bufsize);
+        status = pb_encode(&stream, msgtype, msg);
+        if (!status) fprintf(stderr, "pb_encode: %s\n", PB_GET_ERROR(&stream));
+        assert(status);
+        msglen3 = stream.bytes_written;
+    }
+    
+    assert(msglen2 == msglen3);
+    assert(memcmp(buf2, buf3, msglen2) == 0);
+    
+    pb_release(msgtype, msg);
+    free_with_check(msg);
+    free_with_check(buf2);
+    free_with_check(buf3);
+}
+
+void do_roundtrips(const uint8_t *data, size_t size, bool expect_valid)
+{
+    size_t initial_alloc_count = get_alloc_count();
+
+    /* Check decoding as static fields */
+    if (do_decode(data, size, sizeof(alltypes_static_AllTypes), alltypes_static_AllTypes_fields, expect_valid))
+    {
+        /* Any message that is decodeable as proto2 should be decodeable as proto3 */
+        do_roundtrip(data, size, sizeof(alltypes_static_AllTypes), alltypes_static_AllTypes_fields);
+        do_roundtrip(data, size, sizeof(alltypes_proto3_static_AllTypes), alltypes_proto3_static_AllTypes_fields);
+    }
+    else if (do_decode(data, size, sizeof(alltypes_proto3_static_AllTypes), alltypes_proto3_static_AllTypes_fields, expect_valid))
+    {
+        do_roundtrip(data, size, sizeof(alltypes_proto3_static_AllTypes), alltypes_proto3_static_AllTypes_fields);
+    }
+
+    /* Check decoding as pointer fields */
+    if (do_decode(data, size, sizeof(alltypes_pointer_AllTypes), alltypes_pointer_AllTypes_fields, expect_valid))
+    {
+        do_roundtrip(data, size, sizeof(alltypes_pointer_AllTypes), alltypes_pointer_AllTypes_fields);
+        do_roundtrip(data, size, sizeof(alltypes_proto3_pointer_AllTypes), alltypes_proto3_pointer_AllTypes_fields);
+    }
+    else if (do_decode(data, size, sizeof(alltypes_proto3_pointer_AllTypes), alltypes_proto3_pointer_AllTypes_fields, expect_valid))
+    {
+        do_roundtrip(data, size, sizeof(alltypes_proto3_pointer_AllTypes), alltypes_proto3_pointer_AllTypes_fields);
+    }
+    
+    assert(get_alloc_count() == initial_alloc_count);
+}
+
+/* Fuzzer stub for Google OSSFuzz integration */
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+    /* In some cases, when we re-encode input it can expand close to 2x.
+     * This is because 0xFFFFFFFF in int32 is decoded as -1, but the
+     * canonical encoding for it is 0xFFFFFFFFFFFFFFFF.
+     * Thus we reserve double the space for intermediate buffers.
+     */
+    if (size > g_bufsize / 2)
+        return 0;
+
+    do_roundtrips(data, size, false);
+
+    return 0;
+}
+
 #ifndef LLVMFUZZER
 
-static uint32_t random_seed;
-
-/* Uses xorshift64 here instead of rand() for both speed and
- * reproducibility across platforms. */
-static uint32_t rand_word()
-{
-    random_seed ^= random_seed << 13;
-    random_seed ^= random_seed >> 17;
-    random_seed ^= random_seed << 5;
-    return random_seed;
-}
-
-/* Get a random integer in range, with approximately flat distribution. */
-static int rand_int(int min, int max)
-{
-    return rand_word() % (max + 1 - min) + min;
-}
-
-static bool rand_bool()
-{
-    return rand_word() & 1;
-}
-
-/* Get a random byte, with skewed distribution.
- * Important corner cases like 0xFF, 0x00 and 0xFE occur more
- * often than other values. */
-static uint8_t rand_byte()
-{
-    uint32_t w = rand_word();
-    uint8_t b = w & 0xFF;
-    if (w & 0x100000)
-        b >>= (w >> 8) & 7;
-    if (w & 0x200000)
-        b <<= (w >> 12) & 7;
-    if (w & 0x400000)
-        b ^= 0xFF;
-    return b;
-}
-
-/* Get a random length, with skewed distribution.
- * Favors the shorter lengths, but always atleast 1. */
-static size_t rand_len(size_t max)
-{
-    uint32_t w = rand_word();
-    size_t s;
-    if (w & 0x800000)
-        w &= 3;
-    else if (w & 0x400000)
-        w &= 15;
-    else if (w & 0x200000)
-        w &= 255;
-
-    s = (w % max);
-    if (s == 0)
-        s = 1;
-    
-    return s;
-}
-
-/* Fills a buffer with random data with skewed distribution. */
-static void rand_fill(uint8_t *buf, size_t count)
-{
-    while (count--)
-        *buf++ = rand_byte();
-}
-
-/* Fill with random protobuf-like data */
-static size_t rand_fill_protobuf(uint8_t *buf, size_t min_bytes, size_t max_bytes, int min_tag)
-{
-    pb_ostream_t stream = pb_ostream_from_buffer(buf, max_bytes);
-
-    while(stream.bytes_written < min_bytes)
-    {
-        pb_wire_type_t wt = rand_int(0, 3);
-        if (wt == 3) wt = 5; /* Gap in values */
-        
-        if (!pb_encode_tag(&stream, wt, rand_int(min_tag, min_tag + 512)))
-            break;
-    
-        if (wt == PB_WT_VARINT)
-        {
-            uint64_t value;
-            rand_fill((uint8_t*)&value, sizeof(value));
-            pb_encode_varint(&stream, value);
-        }
-        else if (wt == PB_WT_64BIT)
-        {
-            uint64_t value;
-            rand_fill((uint8_t*)&value, sizeof(value));
-            pb_encode_fixed64(&stream, &value);
-        }
-        else if (wt == PB_WT_32BIT)
-        {
-            uint32_t value;
-            rand_fill((uint8_t*)&value, sizeof(value));
-            pb_encode_fixed32(&stream, &value);
-        }
-        else if (wt == PB_WT_STRING)
-        {
-            size_t len;
-            uint8_t *buf;
-            
-            if (min_bytes > stream.bytes_written)
-                len = rand_len(min_bytes - stream.bytes_written);
-            else
-                len = 0;
-            
-            buf = malloc(len);
-            pb_encode_varint(&stream, len);
-            rand_fill(buf, len);
-            pb_write(&stream, buf, len);
-            free(buf);
-        }
-    }
-    
-    return stream.bytes_written;
-}
-
-/* Given a buffer of data, mess it up a bit */
-static void rand_mess(uint8_t *buf, size_t count)
-{
-    int m = rand_int(0, 3);
-    
-    if (m == 0)
-    {
-        /* Replace random substring */
-        int s = rand_int(0, count - 1);
-        int l = rand_len(count - s);
-        rand_fill(buf + s, l);
-    }
-    else if (m == 1)
-    {
-        /* Swap random bytes */
-        int a = rand_int(0, count - 1);
-        int b = rand_int(0, count - 1);
-        int x = buf[a];
-        buf[a] = buf[b];
-        buf[b] = x;
-    }
-    else if (m == 2)
-    {
-        /* Duplicate substring */
-        int s = rand_int(0, count - 2);
-        int l = rand_len((count - s) / 2);
-        memcpy(buf + s + l, buf + s, l);
-    }
-    else if (m == 3)
-    {
-        /* Add random protobuf noise */
-        int s = rand_int(0, count - 1);
-        int l = rand_len(count - s);
-        rand_fill_protobuf(buf + s, l, count - s, 1);
-    }
-}
-
-/* Append or prepend protobuf noise */
-static void do_protobuf_noise(uint8_t *buffer, size_t *msglen)
-{
-    int m = rand_int(0, 2);
-    size_t max_size = g_bufsize - 32 - *msglen;
-    if (m == 1)
-    {
-        /* Prepend */
-        uint8_t *tmp = malloc_with_check(g_bufsize);
-        size_t s = rand_fill_protobuf(tmp, rand_len(max_size), g_bufsize - *msglen, 1000);
-        memmove(buffer + s, buffer, *msglen);
-        memcpy(buffer, tmp, s);
-        free_with_check(tmp);
-        *msglen += s;
-    }
-    else if (m == 2)
-    {
-        /* Append */
-        size_t s = rand_fill_protobuf(buffer + *msglen, rand_len(max_size), g_bufsize - *msglen, 1000);
-        *msglen += s;
-    }
-}
-
-/* Some default data to put in the message */
-static const alltypes_static_AllTypes initval = alltypes_static_AllTypes_init_default;
-
-static bool do_static_encode(uint8_t *buffer, size_t *msglen)
+static bool generate_base_message(uint8_t *buffer, size_t *msglen)
 {
     pb_ostream_t stream;
     bool status;
+    static const alltypes_static_AllTypes initval = alltypes_static_AllTypes_init_default;
 
     /* Allocate a message and fill it with defaults */
     alltypes_static_AllTypes *msg = malloc_with_check(sizeof(alltypes_static_AllTypes));
@@ -230,265 +194,21 @@
     return status;
 }
 
-#endif
-
-/* Check the invariants defined in security model on decoded structure */
-static void sanity_check_static(alltypes_static_AllTypes *msg)
-{
-    bool truebool = true;
-    bool falsebool = false;
-
-    /* TODO: Add more checks, or rather, generate them automatically */
-    assert(strlen(msg->req_string) < sizeof(msg->req_string));
-    assert(strlen(msg->opt_string) < sizeof(msg->opt_string));
-    if (msg->rep_string_count > 0)
-    {
-        assert(strlen(msg->rep_string[0]) < sizeof(msg->rep_string[0]));
-    }
-    assert(memcmp(&msg->req_bool, &truebool, sizeof(bool)) == 0 ||
-           memcmp(&msg->req_bool, &falsebool, sizeof(bool)) == 0);
-    assert(memcmp(&msg->has_opt_bool, &truebool, sizeof(bool)) == 0 ||
-           memcmp(&msg->has_opt_bool, &falsebool, sizeof(bool)) == 0);
-    assert(memcmp(&msg->opt_bool, &truebool, sizeof(bool)) == 0 ||
-           memcmp(&msg->opt_bool, &falsebool, sizeof(bool)) == 0);
-    assert(msg->rep_bool_count <= pb_arraysize(alltypes_static_AllTypes, rep_bool));
-    if (msg->rep_bool_count > 0)
-    {
-        assert(memcmp(&msg->rep_bool[0], &truebool, sizeof(bool)) == 0 ||
-               memcmp(&msg->rep_bool[0], &falsebool, sizeof(bool)) == 0);
-    }
-}
-
-static bool do_static_decode(const uint8_t *buffer, size_t msglen, bool assert_success)
-{
-    pb_istream_t stream;
-    bool status_proto2, status_proto3;
-    
-    {
-        alltypes_static_AllTypes *msg = malloc_with_check(sizeof(alltypes_static_AllTypes));
-#ifdef LLVMFUZZER
-        memset(msg, 0xAA, sizeof(alltypes_static_AllTypes));
-#else
-        rand_fill((uint8_t*)msg, sizeof(alltypes_static_AllTypes));
-#endif
-        stream = pb_istream_from_buffer(buffer, msglen);
-        status_proto2 = pb_decode(&stream, alltypes_static_AllTypes_fields, msg);
-
-        if (status_proto2)
-        {
-            sanity_check_static(msg);
-        }
-
-        if (assert_success)
-        {
-            if (!status_proto2) fprintf(stderr, "pb_decode: %s\n", PB_GET_ERROR(&stream));
-            assert(status_proto2);
-        }
-
-        free_with_check(msg);
-    }
-    
-    {
-        alltypes_proto3_static_AllTypes *msg = malloc_with_check(sizeof(alltypes_proto3_static_AllTypes));
-#ifdef LLVMFUZZER
-        memset(msg, 0xAA, sizeof(alltypes_proto3_static_AllTypes));
-#else
-        rand_fill((uint8_t*)msg, sizeof(alltypes_proto3_static_AllTypes));
-#endif
-        stream = pb_istream_from_buffer(buffer, msglen);
-        status_proto3 = pb_decode(&stream, alltypes_proto3_static_AllTypes_fields, msg);
-
-        if (status_proto2)
-        {
-            /* Anything decodeable as the proto2 message should be decodeable as proto3 also */
-            if (!status_proto3) fprintf(stderr, "pb_decode: %s\n", PB_GET_ERROR(&stream));
-            assert(status_proto3);
-        }
-
-        free_with_check(msg);
-    }
-    
-    return status_proto2;
-}
-
-static bool do_pointer_decode(const uint8_t *buffer, size_t msglen, bool assert_success)
-{
-    pb_istream_t stream;
-    bool status_proto2, status_proto3;
-    size_t initial_alloc_count;
-    
-    {
-        /* For proto2 syntax message */
-        alltypes_pointer_AllTypes *msg;
-
-        msg = malloc_with_check(sizeof(alltypes_pointer_AllTypes));
-        memset(msg, 0, sizeof(alltypes_pointer_AllTypes));
-        stream = pb_istream_from_buffer(buffer, msglen);
-
-        initial_alloc_count = get_alloc_count();
-        status_proto2 = pb_decode(&stream, alltypes_pointer_AllTypes_fields, msg);
-
-        if (assert_success)
-        {
-            if (!status_proto2) fprintf(stderr, "pb_decode: %s\n", PB_GET_ERROR(&stream));
-            assert(status_proto2);
-        }
-
-        pb_release(alltypes_pointer_AllTypes_fields, msg);
-        assert(get_alloc_count() == initial_alloc_count);
-
-        free_with_check(msg);
-    }
-
-    {
-        /* For proto3 syntax message */
-        alltypes_proto3_pointer_AllTypes *msg;
-
-        msg = malloc_with_check(sizeof(alltypes_proto3_pointer_AllTypes));
-        memset(msg, 0, sizeof(alltypes_proto3_pointer_AllTypes));
-        stream = pb_istream_from_buffer(buffer, msglen);
-
-        initial_alloc_count = get_alloc_count();
-        status_proto3 = pb_decode(&stream, alltypes_proto3_pointer_AllTypes_fields, msg);
-
-        if (status_proto2)
-        {
-            /* Anything decodeable as the proto2 message should be decodeable as proto3 also */
-            if (!status_proto3) fprintf(stderr, "pb_decode: %s\n", PB_GET_ERROR(&stream));
-            assert(status_proto3);
-        }
-
-        pb_release(alltypes_proto3_pointer_AllTypes_fields, msg);
-        assert(get_alloc_count() == initial_alloc_count);
-
-        free_with_check(msg);
-    }
-
-    return status_proto2;
-}
-
-/* Do a decode -> encode -> decode -> encode roundtrip */
-static void do_roundtrip(const uint8_t *buffer, size_t msglen, size_t structsize, const pb_msgdesc_t *msgtype)
-{
-    bool status;
-    uint8_t *buf2 = malloc_with_check(g_bufsize);
-    uint8_t *buf3 = malloc_with_check(g_bufsize);
-    size_t msglen2, msglen3;
-    void *msg = malloc_with_check(structsize);
-    
-    {
-        pb_istream_t stream = pb_istream_from_buffer(buffer, msglen);
-        memset(msg, 0, structsize);
-        status = pb_decode(&stream, msgtype, msg);
-        if (!status) fprintf(stderr, "pb_decode: %s\n", PB_GET_ERROR(&stream));
-        assert(status);
-
-        if (msgtype == alltypes_static_AllTypes_fields)
-        {
-            sanity_check_static(msg);
-        }
-    }
-    
-    {
-        pb_ostream_t stream = pb_ostream_from_buffer(buf2, g_bufsize);
-        status = pb_encode(&stream, msgtype, msg);
-        if (!status) fprintf(stderr, "pb_encode: %s\n", PB_GET_ERROR(&stream));
-        assert(status);
-        msglen2 = stream.bytes_written;
-    }
-    
-    pb_release(msgtype, msg);
-
-    {
-        pb_istream_t stream = pb_istream_from_buffer(buf2, msglen2);
-        memset(msg, 0, structsize);
-        status = pb_decode(&stream, msgtype, msg);
-        if (!status) fprintf(stderr, "pb_decode: %s\n", PB_GET_ERROR(&stream));
-        assert(status);
-
-        if (msgtype == alltypes_static_AllTypes_fields)
-        {
-            sanity_check_static(msg);
-        }
-    }
-    
-    {
-        pb_ostream_t stream = pb_ostream_from_buffer(buf3, g_bufsize);
-        status = pb_encode(&stream, msgtype, msg);
-        if (!status) fprintf(stderr, "pb_encode: %s\n", PB_GET_ERROR(&stream));
-        assert(status);
-        msglen3 = stream.bytes_written;
-    }
-    
-    assert(msglen2 == msglen3);
-    assert(memcmp(buf2, buf3, msglen2) == 0);
-    
-    pb_release(msgtype, msg);
-    free_with_check(msg);
-    free_with_check(buf2);
-    free_with_check(buf3);
-}
-
-/* Fuzzer stub for Google OSSFuzz integration */
-int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
-{
-#ifndef __AVR__
-    g_bufsize = 65536;
-#endif
-
-    /* In some cases, when we re-encode input it can expand close to 2x.
-     * This is because 0xFFFFFFFF in int32 is decoded as -1, but the
-     * canonical encoding for it is 0xFFFFFFFFFFFFFFFF.
-     * Thus we reserve 64k for the buffers but reject inputs longer than 32k.
-     */
-    if (size > 32767)
-        return 0;
-
-    if (do_static_decode(data, size, false))
-    {
-        do_roundtrip(data, size, sizeof(alltypes_static_AllTypes), alltypes_static_AllTypes_fields);
-        do_roundtrip(data, size, sizeof(alltypes_proto3_static_AllTypes), alltypes_proto3_static_AllTypes_fields);
-    }
-
-    if (do_pointer_decode(data, size, false))
-    {
-        do_roundtrip(data, size, sizeof(alltypes_pointer_AllTypes), alltypes_pointer_AllTypes_fields);
-        do_roundtrip(data, size, sizeof(alltypes_proto3_pointer_AllTypes), alltypes_proto3_pointer_AllTypes_fields);
-    }
-
-    return 0;
-}
-
-#ifndef LLVMFUZZER
-
 /* Stand-alone fuzzer iteration, generates random data itself */
 static void run_iteration()
 {
     uint8_t *buffer = malloc_with_check(g_bufsize);
     size_t msglen;
-    bool status;
     
+    /* Fill the whole buffer with noise, to prepare for length modifications */
     rand_fill(buffer, g_bufsize);
 
-    if (do_static_encode(buffer, &msglen))
+    if (generate_base_message(buffer, &msglen))
     {
-        do_protobuf_noise(buffer, &msglen);
+        rand_protobuf_noise(buffer, g_bufsize, &msglen);
     
-        status = do_static_decode(buffer, msglen, true);
-        
-        if (status)
-        {
-            do_roundtrip(buffer, msglen, sizeof(alltypes_static_AllTypes), alltypes_static_AllTypes_fields);
-            do_roundtrip(buffer, msglen, sizeof(alltypes_proto3_static_AllTypes), alltypes_proto3_static_AllTypes_fields);
-        }
-        
-        status = do_pointer_decode(buffer, msglen, true);
-        
-        if (status)
-        {
-            do_roundtrip(buffer, msglen, sizeof(alltypes_pointer_AllTypes), alltypes_pointer_AllTypes_fields);
-            do_roundtrip(buffer, msglen, sizeof(alltypes_proto3_pointer_AllTypes), alltypes_proto3_pointer_AllTypes_fields);
-        }
+        /* At this point the message should always be valid */
+        do_roundtrips(buffer, msglen, true);
         
         /* Apply randomness to the encoded data */
         while (rand_bool())
@@ -498,16 +218,8 @@
         if (rand_bool())
             msglen = rand_int(0, g_bufsize);
         
-        status = do_static_decode(buffer, msglen, false);
-        do_pointer_decode(buffer, msglen, status);
-        
-        if (status)
-        {
-            do_roundtrip(buffer, msglen, sizeof(alltypes_static_AllTypes), alltypes_static_AllTypes_fields);
-            do_roundtrip(buffer, msglen, sizeof(alltypes_proto3_static_AllTypes), alltypes_proto3_static_AllTypes_fields);
-            do_roundtrip(buffer, msglen, sizeof(alltypes_pointer_AllTypes), alltypes_pointer_AllTypes_fields);
-            do_roundtrip(buffer, msglen, sizeof(alltypes_proto3_pointer_AllTypes), alltypes_proto3_pointer_AllTypes_fields);
-        }
+        /* In this step the message may be valid or invalid */
+        do_roundtrips(buffer, msglen, false);
     }
     
     free_with_check(buffer);
@@ -522,12 +234,12 @@
     if (argc >= 2)
     {
         /* Run in stand-alone mode */
-        random_seed = strtoul(argv[1], NULL, 0);
+        random_set_seed(strtoul(argv[1], NULL, 0));
         iterations = (argc >= 3) ? atol(argv[2]) : 10000;
 
         for (i = 0; i < iterations; i++)
         {
-            printf("Iteration %d/%d, seed %lu\n", i, iterations, (unsigned long)random_seed);
+            printf("Iteration %d/%d, seed %lu\n", i, iterations, (unsigned long)random_get_seed());
             run_iteration();
         }
     }
@@ -537,10 +249,6 @@
         uint8_t *buffer;
         size_t msglen;
 
-#ifndef __AVR__
-        g_bufsize = 65536;
-#endif
-
         buffer = malloc_with_check(g_bufsize);
 
         SET_BINARY_MODE(stdin);
diff --git a/tests/fuzztest/generate_message.c b/tests/fuzztest/generate_message.c
index 4c21a02..42183f5 100644
--- a/tests/fuzztest/generate_message.c
+++ b/tests/fuzztest/generate_message.c
@@ -9,27 +9,7 @@
 #include <string.h>
 #include <assert.h>
 #include "alltypes_static.pb.h"
-
-static uint64_t random_seed;
-
-/* Uses xorshift64 here instead of rand() for both speed and
- * reproducibility across platforms. */
-static uint32_t rand_word()
-{
-    random_seed ^= random_seed >> 12;
-    random_seed ^= random_seed << 25;
-    random_seed ^= random_seed >> 27;
-    return random_seed * 2685821657736338717ULL;
-}
-
-/* Fills a buffer with random data. */
-static void rand_fill(uint8_t *buf, size_t count)
-{
-    while (count--)
-    {
-        *buf++ = rand_word() & 0xff;
-    }
-}
+#include "random_data.h"
 
 /* Check that size/count fields do not exceed their max size.
  * Otherwise we would have to loop pretty long in generate_message().
@@ -88,9 +68,9 @@
         return 1;
     }
 
-    random_seed = atol(argv[1]);
+    random_set_seed(atol(argv[1]));
 
-    fprintf(stderr, "Random seed: %u\n", (unsigned)random_seed);
+    fprintf(stderr, "Random seed: %u\n", (unsigned)random_get_seed());
     
     generate_message();
     
diff --git a/tests/fuzztest/random_data.c b/tests/fuzztest/random_data.c
new file mode 100644
index 0000000..726de83
--- /dev/null
+++ b/tests/fuzztest/random_data.c
@@ -0,0 +1,196 @@
+#include "random_data.h"
+#include <string.h>
+#include <malloc_wrappers.h>
+#include <pb_encode.h>
+
+#ifndef LLVMFUZZER
+
+static uint32_t g_random_seed = 1234;
+
+void random_set_seed(uint32_t seed)
+{
+    g_random_seed = seed;
+}
+
+uint32_t random_get_seed()
+{
+    return g_random_seed;
+}
+
+/* Uses xorshift64 here instead of rand() for both speed and
+ * reproducibility across platforms. */
+uint32_t rand_word()
+{
+    g_random_seed ^= g_random_seed << 13;
+    g_random_seed ^= g_random_seed >> 17;
+    g_random_seed ^= g_random_seed << 5;
+    return g_random_seed;
+}
+
+/* Get a random integer in range, with approximately flat distribution. */
+int rand_int(int min, int max)
+{
+    return rand_word() % (max + 1 - min) + min;
+}
+
+bool rand_bool()
+{
+    return rand_word() & 1;
+}
+
+/* Get a random byte, with skewed distribution.
+ * Important corner cases like 0xFF, 0x00 and 0xFE occur more
+ * often than other values. */
+uint8_t rand_byte()
+{
+    uint32_t w = rand_word();
+    uint8_t b = w & 0xFF;
+    if (w & 0x100000)
+        b >>= (w >> 8) & 7;
+    if (w & 0x200000)
+        b <<= (w >> 12) & 7;
+    if (w & 0x400000)
+        b ^= 0xFF;
+    return b;
+}
+
+/* Get a random length, with skewed distribution.
+ * Favors the shorter lengths, but always atleast 1. */
+size_t rand_len(size_t max)
+{
+    uint32_t w = rand_word();
+    size_t s;
+    if (w & 0x800000)
+        w &= 3;
+    else if (w & 0x400000)
+        w &= 15;
+    else if (w & 0x200000)
+        w &= 255;
+
+    s = (w % max);
+    if (s == 0)
+        s = 1;
+
+    return s;
+}
+
+/* Fills a buffer with random data with skewed distribution. */
+void rand_fill(uint8_t *buf, size_t count)
+{
+    while (count--)
+        *buf++ = rand_byte();
+}
+
+/* Fill with random protobuf-like data */
+size_t rand_fill_protobuf(uint8_t *buf, size_t min_bytes, size_t max_bytes, int min_tag)
+{
+    pb_ostream_t stream = pb_ostream_from_buffer(buf, max_bytes);
+
+    while(stream.bytes_written < min_bytes)
+    {
+        pb_wire_type_t wt = rand_int(0, 3);
+        if (wt == 3) wt = 5; /* Gap in values */
+
+        if (!pb_encode_tag(&stream, wt, rand_int(min_tag, min_tag + 512)))
+            break;
+
+        if (wt == PB_WT_VARINT)
+        {
+            uint64_t value;
+            rand_fill((uint8_t*)&value, sizeof(value));
+            pb_encode_varint(&stream, value);
+        }
+        else if (wt == PB_WT_64BIT)
+        {
+            uint64_t value;
+            rand_fill((uint8_t*)&value, sizeof(value));
+            pb_encode_fixed64(&stream, &value);
+        }
+        else if (wt == PB_WT_32BIT)
+        {
+            uint32_t value;
+            rand_fill((uint8_t*)&value, sizeof(value));
+            pb_encode_fixed32(&stream, &value);
+        }
+        else if (wt == PB_WT_STRING)
+        {
+            size_t len;
+            uint8_t *buf;
+
+            if (min_bytes > stream.bytes_written)
+                len = rand_len(min_bytes - stream.bytes_written);
+            else
+                len = 0;
+
+            buf = malloc(len);
+            pb_encode_varint(&stream, len);
+            rand_fill(buf, len);
+            pb_write(&stream, buf, len);
+            free(buf);
+        }
+    }
+
+    return stream.bytes_written;
+}
+
+/* Given a buffer of data, mess it up a bit */
+void rand_mess(uint8_t *buf, size_t count)
+{
+    int m = rand_int(0, 3);
+
+    if (m == 0)
+    {
+        /* Replace random substring */
+        int s = rand_int(0, count - 1);
+        int l = rand_len(count - s);
+        rand_fill(buf + s, l);
+    }
+    else if (m == 1)
+    {
+        /* Swap random bytes */
+        int a = rand_int(0, count - 1);
+        int b = rand_int(0, count - 1);
+        int x = buf[a];
+        buf[a] = buf[b];
+        buf[b] = x;
+    }
+    else if (m == 2)
+    {
+        /* Duplicate substring */
+        int s = rand_int(0, count - 2);
+        int l = rand_len((count - s) / 2);
+        memcpy(buf + s + l, buf + s, l);
+    }
+    else if (m == 3)
+    {
+        /* Add random protobuf noise */
+        int s = rand_int(0, count - 1);
+        int l = rand_len(count - s);
+        rand_fill_protobuf(buf + s, l, count - s, 1);
+    }
+}
+
+/* Append or prepend protobuf noise */
+void rand_protobuf_noise(uint8_t *buffer, size_t bufsize, size_t *msglen)
+{
+    int m = rand_int(0, 2);
+    size_t max_size = bufsize - 32 - *msglen;
+    if (m == 1)
+    {
+        /* Prepend */
+        uint8_t *tmp = malloc_with_check(bufsize);
+        size_t s = rand_fill_protobuf(tmp, rand_len(max_size), bufsize - *msglen, 1000);
+        memmove(buffer + s, buffer, *msglen);
+        memcpy(buffer, tmp, s);
+        free_with_check(tmp);
+        *msglen += s;
+    }
+    else if (m == 2)
+    {
+        /* Append */
+        size_t s = rand_fill_protobuf(buffer + *msglen, rand_len(max_size), bufsize - *msglen, 1000);
+        *msglen += s;
+    }
+}
+
+#endif
diff --git a/tests/fuzztest/random_data.h b/tests/fuzztest/random_data.h
new file mode 100644
index 0000000..9144d49
--- /dev/null
+++ b/tests/fuzztest/random_data.h
@@ -0,0 +1,46 @@
+/* This module handles generating & modifying messages randomly for the fuzz test. */
+
+#ifndef RANDOM_DATA_H
+#define RANDOM_DATA_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+void random_set_seed(uint32_t seed);
+uint32_t random_get_seed();
+
+/* Random 32-bit integer */
+uint32_t rand_word();
+
+/* Get a random integer in range, with approximately flat distribution. */
+int rand_int(int min, int max);
+
+/* Random boolean, equal probability */
+bool rand_bool();
+
+/* Get a random byte, with skewed distribution.
+ * Important corner cases like 0xFF, 0x00 and 0xFE occur more
+ * often than other values. */
+uint8_t rand_byte();
+
+/* Get a random length, with skewed distribution.
+ * Favors the shorter lengths, but always atleast 1. */
+size_t rand_len(size_t max);
+
+/* Fills a buffer with random bytes with skewed distribution. */
+void rand_fill(uint8_t *buf, size_t count);
+
+/* Fill with random protobuf-like data */
+size_t rand_fill_protobuf(uint8_t *buf, size_t min_bytes, size_t max_bytes, int min_tag);
+
+/* Given a buffer of data, mess it up a bit by copying / swapping bytes */
+void rand_mess(uint8_t *buf, size_t count);
+
+/* Append or prepend protobuf noise, with tag values > 1000 */
+void rand_protobuf_noise(uint8_t *buffer, size_t bufsize, size_t *msglen);
+
+
+
+
+#endif
diff --git a/tests/fuzztest/validation.c b/tests/fuzztest/validation.c
new file mode 100644
index 0000000..ec3486b
--- /dev/null
+++ b/tests/fuzztest/validation.c
@@ -0,0 +1,39 @@
+#include "validation.h"
+#include "alltypes_static.pb.h"
+#include <assert.h>
+
+/* Check the invariants defined in security model on decoded structure */
+static void sanity_check_static(const alltypes_static_AllTypes *msg)
+{
+    bool truebool = true;
+    bool falsebool = false;
+
+    /* TODO: Add more checks, or rather, generate them automatically */
+    assert(strlen(msg->req_string) < sizeof(msg->req_string));
+    assert(strlen(msg->opt_string) < sizeof(msg->opt_string));
+    if (msg->rep_string_count > 0)
+    {
+        assert(strlen(msg->rep_string[0]) < sizeof(msg->rep_string[0]));
+    }
+    assert(memcmp(&msg->req_bool, &truebool, sizeof(bool)) == 0 ||
+           memcmp(&msg->req_bool, &falsebool, sizeof(bool)) == 0);
+    assert(memcmp(&msg->has_opt_bool, &truebool, sizeof(bool)) == 0 ||
+           memcmp(&msg->has_opt_bool, &falsebool, sizeof(bool)) == 0);
+    assert(memcmp(&msg->opt_bool, &truebool, sizeof(bool)) == 0 ||
+           memcmp(&msg->opt_bool, &falsebool, sizeof(bool)) == 0);
+    assert(msg->rep_bool_count <= pb_arraysize(alltypes_static_AllTypes, rep_bool));
+    if (msg->rep_bool_count > 0)
+    {
+        assert(memcmp(&msg->rep_bool[0], &truebool, sizeof(bool)) == 0 ||
+               memcmp(&msg->rep_bool[0], &falsebool, sizeof(bool)) == 0);
+    }
+}
+
+void validate_message(const void *msg, size_t structsize, const pb_msgdesc_t *msgtype)
+{
+    if (msgtype == alltypes_static_AllTypes_fields)
+    {
+        sanity_check_static(msg);
+    }
+}
+
diff --git a/tests/fuzztest/validation.h b/tests/fuzztest/validation.h
new file mode 100644
index 0000000..a5fb910
--- /dev/null
+++ b/tests/fuzztest/validation.h
@@ -0,0 +1,12 @@
+/* This module validates that the message structures are in valid state
+ * after decoding the input data. */
+ 
+#ifndef VALIDATION_H
+#define VALIDATION_H
+
+#include <pb.h>
+
+void validate_message(const void *msg, size_t structsize, const pb_msgdesc_t *msgtype);
+
+#endif
+