Merge pull request #465 from prusnak/validate-utf8

Introduce new compile time flag: PB_VALIDATE_UTF8
diff --git a/extra/nanopb.mk b/extra/nanopb.mk
index 5c2cff5..16bc376 100644
--- a/extra/nanopb.mk
+++ b/extra/nanopb.mk
@@ -23,10 +23,11 @@
 	PROTOC_OPTS = 
 else
 	# Source only or git checkout
-	PROTOC = protoc
 	ifdef WINDOWS
-		PROTOC_OPTS = --plugin=protoc-gen-nanopb=$(NANOPB_DIR)/generator/protoc-gen-nanopb.bat
+	    PROTOC = "python $(NANOPB_DIR)/generator/protoc"
+	    PROTOC_OPTS = --plugin=protoc-gen-nanopb=$(NANOPB_DIR)/generator/protoc-gen-nanopb.bat
 	else
+	    PROTOC = $(NANOPB_DIR)/generator/protoc
 		PROTOC_OPTS = --plugin=protoc-gen-nanopb=$(NANOPB_DIR)/generator/protoc-gen-nanopb
 	endif
 endif
diff --git a/extra/poetry/pyproject.toml b/extra/poetry/pyproject.toml
index cc1a5bd..5b33dfe 100644
--- a/extra/poetry/pyproject.toml
+++ b/extra/poetry/pyproject.toml
@@ -18,9 +18,13 @@
 [tool.poetry.dependencies]
 python = ">=2.7"
 protobuf = ">=3.6"
+grpcio-tools = {version = ">=1.26.0rc1", allow-prereleases = true, optional=true}
 
 [tool.poetry.dev-dependencies]
 
+[tool.poetry.extras]
+grpcio-tools = ["grpcio-tools"]
+
 [build-system]
 requires = ["poetry>=0.12"]
 build-backend = "poetry.masonry.api"
diff --git a/generator/nanopb_generator.py b/generator/nanopb_generator.py
index 87802cd..d14860d 100755
--- a/generator/nanopb_generator.py
+++ b/generator/nanopb_generator.py
@@ -10,6 +10,8 @@
 import re
 import codecs
 import copy
+import tempfile
+import os
 from functools import reduce
 
 try:
@@ -38,6 +40,7 @@
 
 try:
     from .proto import nanopb_pb2
+    from .proto._utils import invoke_protoc
 except TypeError:
     sys.stderr.write('''
          ****************************************************************************
@@ -58,6 +61,7 @@
 except (ValueError, SystemError):
     # Probably invoked directly instead of via installed scripts.
     import proto.nanopb_pb2 as nanopb_pb2
+    from proto._utils import invoke_protoc
 except:
     sys.stderr.write('''
          ********************************************************************
@@ -1833,9 +1837,31 @@
         sys.stderr.write('Google Python protobuf library imported from %s, version %s\n'
                          % (google.protobuf.__file__, google.protobuf.__version__))
 
-    Globals.verbose_options = options.verbose
+    # Load .pb files into memory and compile any .proto files.
+    fdescs = {}
+    include_path = ['-I%s' % p for p in options.options_path]
     for filename in filenames:
-        results = process_file(filename, None, options)
+        if filename.endswith(".proto"):
+            with tempfile.NamedTemporaryFile(suffix = ".pb") as tmp:
+                invoke_protoc(["protoc"] + include_path + ['-o' + tmp.name, filename])
+                data = tmp.read()
+        else:
+            data = open(filename, 'rb').read()
+
+        open("debug", "wb").write(data)
+        fdesc = descriptor.FileDescriptorSet.FromString(data).file[0]
+        fdescs[fdesc.name] = fdesc
+
+    # Process any include files first, in order to have them
+    # available as dependencies
+    other_files = {}
+    for fdesc in fdescs.values():
+        other_files[fdesc.name] = parse_file(fdesc.name, fdesc, options)
+
+    # Then generate the headers / sources
+    Globals.verbose_options = options.verbose
+    for fdesc in fdescs.values():
+        results = process_file(fdesc.name, fdesc, options, other_files)
 
         base_dir = options.output_dir or ''
         to_write = [
diff --git a/generator/proto/__init__.py b/generator/proto/__init__.py
index 93e7b33..29153d4 100644
--- a/generator/proto/__init__.py
+++ b/generator/proto/__init__.py
@@ -1,9 +1,12 @@
 '''This file automatically rebuilds the proto definitions for Python.'''
+from __future__ import absolute_import
 
-import os
 import os.path
 import sys
-import subprocess
+
+import pkg_resources
+
+from ._utils import has_grpcio_protoc, invoke_protoc
 
 dirname = os.path.dirname(__file__)
 protosrc = os.path.join(dirname, "nanopb.proto")
@@ -12,8 +15,22 @@
 if os.path.isfile(protosrc):
     src_date = os.path.getmtime(protosrc)
     if not os.path.isfile(protodst) or os.path.getmtime(protodst) < src_date:
-        cmd = ["protoc", "--python_out=.", "nanopb.proto"]
-        status = subprocess.call(cmd, cwd = dirname)
-        if status != 0:
-            sys.stderr.write("Failed to build nanopb_pb2.py: " + ' '.join(cmd) + "\n")
 
+        cmd = [
+            "protoc",
+            "--python_out={}".format(dirname),
+            protosrc,
+            "-I={}".format(dirname),
+        ]
+
+        if has_grpcio_protoc():
+            # grpcio-tools has an extra CLI argument
+            # from grpc.tools.protoc __main__ invocation.
+            _builtin_proto_include = pkg_resources.resource_filename('grpc_tools', '_proto')
+
+            cmd.append("-I={}".format(_builtin_proto_include))
+        try:
+            invoke_protoc(argv=cmd)
+        except:
+            sys.stderr.write("Failed to build nanopb_pb2.py: " + ' '.join(cmd) + "\n")
+            raise
diff --git a/generator/proto/_utils.py b/generator/proto/_utils.py
new file mode 100644
index 0000000..26e1ccf
--- /dev/null
+++ b/generator/proto/_utils.py
@@ -0,0 +1,29 @@
+import subprocess
+
+def has_grpcio_protoc():
+    # type: () -> bool
+    """ checks if grpcio-tools protoc is installed"""
+
+    try:
+        import grpc_tools.protoc
+    except ImportError:
+        return False
+    return True
+
+
+def invoke_protoc(argv):
+    # type: (list) -> typing.Any
+    """
+    Invoke protoc.
+
+    This routine will use grpcio-provided protoc if it exists,
+    using system-installed protoc as a fallback.
+
+    Args:
+        argv: protoc CLI invocation, first item must be 'protoc'
+    """
+    if has_grpcio_protoc():
+        import grpc_tools.protoc as protoc
+        return protoc.main(argv)
+    else:
+        return subprocess.check_call(argv)
diff --git a/generator/protoc b/generator/protoc
new file mode 100755
index 0000000..15f1d81
--- /dev/null
+++ b/generator/protoc
@@ -0,0 +1,8 @@
+#!/usr/bin/env python
+
+import sys
+from nanopb_generator import invoke_protoc
+
+if __name__ == '__main__':
+    status = invoke_protoc(['protoc'] + sys.argv[1:])
+    sys.exit(status)
diff --git a/tests/SConstruct b/tests/SConstruct
index 6601764..e2e337d 100644
--- a/tests/SConstruct
+++ b/tests/SConstruct
@@ -8,6 +8,11 @@
 CXX         Name of C++ compiler
 CCFLAGS     Flags to pass to the C compiler
 CXXFLAGS    Flags to pass to the C++ compiler
+LINKFLAGS   Flags to pass to linker
+LINKLIBS    Flags to pass to linker after object files
+PROTOC      Path to protoc binary
+PROTOCFLAGS Arguments to pass protoc
+NODEFARGS   Do not add the default CCFLAGS
 
 For example, for a clang build, use:
 scons CC=clang CXX=clang++
@@ -34,6 +39,11 @@
 if 'CXX' in ARGUMENTS: env.Replace(CXX = ARGUMENTS['CXX'])
 if 'CCFLAGS' in ARGUMENTS: env.Append(CCFLAGS = ARGUMENTS['CCFLAGS'])
 if 'CXXFLAGS' in ARGUMENTS: env.Append(CXXFLAGS = ARGUMENTS['CXXFLAGS'])
+if 'LINKFLAGS' in ARGUMENTS: env.Append(LINKFLAGS = ARGUMENTS['LINKFLAGS'])
+if 'LINKLIBS' in ARGUMENTS: env.Append(LINKLIBS = ARGUMENTS['LINKLIBS'])
+if 'PROTOC' in ARGUMENTS: env.Replace(PROTOC = ARGUMENTS['PROTOC'])
+if 'PROTOCFLAGS' in ARGUMENTS: env.Replace(PROTOCFLAGS = ARGUMENTS['PROTOCFLAGS'])
+if 'NODEFARGS' in ARGUMENTS: env.Replace(NODEFARGS = ARGUMENTS['NODEFARGS'])
 
 # Add the builders defined in site_init.py
 add_nanopb_builders(env)
@@ -92,91 +102,97 @@
     if status:
         conf.env['PROTOC_VERSION'] = output
 
-    # Check if libmudflap is available (only with GCC)
-    if 'gcc' in env['CC']:
-        if conf.CheckLib('mudflap'):
-            conf.env.Append(CCFLAGS = '-fmudflap')
-            conf.env.Append(LINKFLAGS = '-fmudflap')
-    
-    # Check if we can use extra strict warning flags (only with GCC)
-    extra = '-Wcast-qual -Wlogical-op -Wconversion'
-    extra += ' -fstrict-aliasing -Wstrict-aliasing=1'
-    extra += ' -Wmissing-prototypes -Wmissing-declarations -Wredundant-decls'
-    extra += ' -Wstack-protector '
-    if 'gcc' in env['CC']:
-        if conf.CheckCCFLAGS(extra):
-            conf.env.Append(CORECFLAGS = extra)
-    
-    # Check if we can use undefined behaviour sanitizer (only with clang)
-    # TODO: Fuzz test triggers the bool sanitizer, figure out whether to
-    #       modify the fuzz test or to keep ignoring the check.
-    extra = '-fsanitize=undefined,integer -fno-sanitize-recover=undefined,integer '
-    if 'clang' in env['CC']:
-        if conf.CheckCCFLAGS(extra, linkflags = extra):
-            conf.env.Append(CORECFLAGS = extra)
-            conf.env.Append(LINKFLAGS = extra)
+    if not env.get('NODEFARGS'):
+        # Check if libmudflap is available (only with GCC)
+        if 'gcc' in env['CC']:
+            if conf.CheckLib('mudflap'):
+                conf.env.Append(CCFLAGS = '-fmudflap')
+                conf.env.Append(LINKFLAGS = '-fmudflap')
+
+        # Check if we can use extra strict warning flags (only with GCC)
+        extra = '-Wcast-qual -Wlogical-op -Wconversion'
+        extra += ' -fstrict-aliasing -Wstrict-aliasing=1'
+        extra += ' -Wmissing-prototypes -Wmissing-declarations -Wredundant-decls'
+        extra += ' -Wstack-protector '
+        if 'gcc' in env['CC']:
+            if conf.CheckCCFLAGS(extra):
+                conf.env.Append(CORECFLAGS = extra)
+
+        # Check if we can use undefined behaviour sanitizer (only with clang)
+        # TODO: Fuzz test triggers the bool sanitizer, figure out whether to
+        #       modify the fuzz test or to keep ignoring the check.
+        extra = '-fsanitize=undefined,integer -fno-sanitize-recover=undefined,integer '
+        if 'clang' in env['CC']:
+            if conf.CheckCCFLAGS(extra, linkflags = extra):
+                conf.env.Append(CORECFLAGS = extra)
+                conf.env.Append(LINKFLAGS = extra)
     
     # End the config stuff
     env = conf.Finish()
 
-# Initialize the CCFLAGS according to the compiler
-if 'gcc' in env['CC']:
-    # GNU Compiler Collection
-    
-    # Debug info, warnings as errors
-    env.Append(CFLAGS = '-g -Wall -Werror ')
-    env.Append(CORECFLAGS = '-Wextra')
-    
-    # Pedantic ANSI C. On AVR this doesn't work because we use large
-    # enums in some of the tests.
-    if env.get("EMBEDDED") != "AVR":
-        env.Append(CFLAGS = '-ansi -pedantic')
-    
-    # Profiling and coverage
-    if not env.get("EMBEDDED"):
-        env.Append(CFLAGS = '-fprofile-arcs -ftest-coverage ')
-        env.Append(LINKFLAGS = '-g --coverage')
+if not env.get('NODEFARGS'):
+    # Initialize the CCFLAGS according to the compiler
+    if 'gcc' in env['CC']:
+        # GNU Compiler Collection
         
-    
-    # We currently need uint64_t anyway, even though ANSI C90 otherwise..
-    env.Append(CFLAGS = '-Wno-long-long')
-elif 'clang' in env['CC']:
-    # CLang
-    env.Append(CFLAGS = '-ansi -g -Wall -Werror')
-    env.Append(CORECFLAGS = ' -Wextra -Wcast-qual -Wconversion')
-elif 'cl' in env['CC']:
-    # Microsoft Visual C++
-    
-    # Debug info on, warning level 2 for tests, warnings as errors
-    env.Append(CFLAGS = '/Zi /W2 /WX')
-    env.Append(LINKFLAGS = '/DEBUG')
-    
-    # More strict checks on the nanopb core
-    env.Append(CORECFLAGS = '/W4')
+        # Debug info, warnings as errors
+        env.Append(CFLAGS = '-g -Wall -Werror ')
+        env.Append(CORECFLAGS = '-Wextra')
 
-    # Disable warning about sizeof(union{}) construct that is used in
-    # message size macros, in e.g. multiple_files testcase. The C construct
-    # itself is valid, but quite rare, which causes Visual C++ to give a warning
-    # about it.
-    env.Append(CFLAGS = '/wd4116')
-elif 'tcc' in env['CC']:
-    # Tiny C Compiler
-    env.Append(CFLAGS = '-Wall -Werror -g')
+        # Pedantic ANSI C. On AVR this doesn't work because we use large
+        # enums in some of the tests.
+        if env.get("EMBEDDED") != "AVR":
+            env.Append(CFLAGS = '-ansi -pedantic')
+
+        # Profiling and coverage
+        if not env.get("EMBEDDED"):
+            env.Append(CFLAGS = '-fprofile-arcs -ftest-coverage ')
+            env.Append(LINKFLAGS = '-g --coverage')
+
+
+        # We currently need uint64_t anyway, even though ANSI C90 otherwise..
+        env.Append(CFLAGS = '-Wno-long-long')
+    elif 'clang' in env['CC']:
+        # CLang
+        env.Append(CFLAGS = '-ansi -g -Wall -Werror')
+        env.Append(CORECFLAGS = ' -Wextra -Wcast-qual -Wconversion')
+    elif 'cl' in env['CC']:
+        # Microsoft Visual C++
+
+        # Debug info on, warning level 2 for tests, warnings as errors
+        env.Append(CFLAGS = '/Zi /W2 /WX')
+        env.Append(LINKFLAGS = '/DEBUG')
+
+        # More strict checks on the nanopb core
+        env.Append(CORECFLAGS = '/W4')
+
+        # Disable warning about sizeof(union{}) construct that is used in
+        # message size macros, in e.g. multiple_files testcase. The C construct
+        # itself is valid, but quite rare, which causes Visual C++ to give a warning
+        # about it.
+        env.Append(CFLAGS = '/wd4116')
+    elif 'tcc' in env['CC']:
+        # Tiny C Compiler
+        env.Append(CFLAGS = '-Wall -Werror -g')
+
+    if 'clang' in env['CXX']:
+        env.Append(CXXFLAGS = '-g -Wall -Werror -Wextra -Wno-missing-field-initializers')
+    elif 'g++' in env['CXX'] or 'gcc' in env['CXX']:
+        env.Append(CXXFLAGS = '-g -Wall -Werror -Wextra -Wno-missing-field-initializers')
+    elif 'cl' in env['CXX']:
+        env.Append(CXXFLAGS = '/Zi /W2 /WX /wd4116 /wd4127')
 
 env.SetDefault(CORECFLAGS = '')
 
-if 'clang' in env['CXX']:
-    env.Append(CXXFLAGS = '-g -Wall -Werror -Wextra -Wno-missing-field-initializers')
-elif 'g++' in env['CXX'] or 'gcc' in env['CXX']:
-    env.Append(CXXFLAGS = '-g -Wall -Werror -Wextra -Wno-missing-field-initializers')
-elif 'cl' in env['CXX']:
-    env.Append(CXXFLAGS = '/Zi /W2 /WX /wd4116 /wd4127')
-
 if not env.get("EMBEDDED"):
     valgrind = env.WhereIs('valgrind')
     if valgrind:
         env.SetDefault(VALGRIND = valgrind)
 
+# Make it possible to add commands to the end of linker line
+env.SetDefault(LINKLIBS = '')
+env.Replace(LINKCOM = env['LINKCOM'] + " $LINKLIBS")
+
 # Now include the SConscript files from all subdirectories
 import os.path
 env['VARIANT_DIR'] = 'build'
diff --git a/tests/float_double_conversion/SConscript b/tests/float_double_conversion/SConscript
index f64a318..21c9a81 100644
--- a/tests/float_double_conversion/SConscript
+++ b/tests/float_double_conversion/SConscript
@@ -6,6 +6,9 @@
 opts = env.Clone()
 opts.Append(CPPDEFINES = {'PB_CONVERT_DOUBLE_FLOAT': 1})
 
+if opts.get('EMBEDDED') == 'AVR':
+    opts.Append(CFLAGS = "-Wno-overflow")
+
 # Build new version of core
 strict = opts.Clone()
 strict.Append(CFLAGS = strict['CORECFLAGS'])
diff --git a/tests/float_double_conversion/float_double_conversion.c b/tests/float_double_conversion/float_double_conversion.c
index eb3b45a..70a3664 100644
--- a/tests/float_double_conversion/float_double_conversion.c
+++ b/tests/float_double_conversion/float_double_conversion.c
@@ -17,7 +17,9 @@
 static const double testvalues[] = {
            0.0,        -0.0,         0.1,         -0.1,
           M_PI,       -M_PI,  123456.789,  -123456.789,
+#if defined(NAN) && defined(INFINITY)
       INFINITY,   -INFINITY,         NAN, INFINITY - INFINITY,
+#endif
           1e38,       -1e38,        1e39,        -1e39,
          1e-38,      -1e-38,       1e-39,       -1e-39,
    3.14159e-37,-3.14159e-37, 3.14159e-43, -3.14159e-43,
diff --git a/tests/fuzztest/SConscript b/tests/fuzztest/SConscript
index 52033e1..7723d99 100644
--- a/tests/fuzztest/SConscript
+++ b/tests/fuzztest/SConscript
@@ -36,14 +36,6 @@
     iterations = 10000
 env.RunTest(fuzz, ARGS = [str(seed), str(iterations)])
 
-fuzzstub = malloc_env.Program(["fuzzstub.c",
-                    "alltypes_pointer.pb.c",
-                    "alltypes_static.pb.c",
-                    "$COMMON/pb_encode_with_malloc.o",
-                    "$COMMON/pb_decode_with_malloc.o",
-                    "$COMMON/pb_common_with_malloc.o",
-                    "$COMMON/malloc_wrappers.o"])
-
 generate_message = malloc_env.Program(["generate_message.c",
                     "alltypes_static.pb.c",
                     "$COMMON/pb_encode.o",
diff --git a/tests/fuzztest/fuzzstub.c b/tests/fuzztest/fuzzstub.c
deleted file mode 100644
index ec9e2af..0000000
--- a/tests/fuzztest/fuzzstub.c
+++ /dev/null
@@ -1,189 +0,0 @@
-/* Fuzz testing for the nanopb core.
- * This can be used with external fuzzers, e.g. radamsa.
- * It performs most of the same checks as fuzztest, but does not feature data generation.
- */
-
-#include <pb_decode.h>
-#include <pb_encode.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-#include <time.h>
-#include <malloc_wrappers.h>
-#include "alltypes_static.pb.h"
-#include "alltypes_pointer.pb.h"
-
-#define BUFSIZE 4096
-
-static bool do_static_decode(uint8_t *buffer, size_t msglen, bool assert_success)
-{
-    pb_istream_t stream;
-    bool status;
-    
-    alltypes_static_AllTypes *msg = malloc_with_check(sizeof(alltypes_static_AllTypes));
-    stream = pb_istream_from_buffer(buffer, msglen);
-    status = pb_decode(&stream, alltypes_static_AllTypes_fields, msg);
-    
-    if (!status && assert_success)
-    {
-        /* Anything that was successfully encoded, should be decodeable.
-         * One exception: strings without null terminator are encoded up
-         * to end of buffer, but refused on decode because the terminator
-         * would not fit. */
-        if (strcmp(stream.errmsg, "string overflow") != 0)
-            assert(status);
-    }
-    
-    free_with_check(msg);
-    return status;
-}
-
-static bool do_pointer_decode(uint8_t *buffer, size_t msglen, bool assert_success)
-{
-    pb_istream_t stream;
-    bool status;
-    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);
-
-    assert(get_alloc_count() == 0);
-    status = pb_decode(&stream, alltypes_pointer_AllTypes_fields, msg);
-    
-    if (assert_success)
-        assert(status);
-    
-    pb_release(alltypes_pointer_AllTypes_fields, msg);    
-    assert(get_alloc_count() == 0);
-    
-    free_with_check(msg);
-
-    return status;
-}
-
-/* Do a decode -> encode -> decode -> encode roundtrip */
-static void do_static_roundtrip(uint8_t *buffer, size_t msglen)
-{
-    bool status;
-    uint8_t *buf2 = malloc_with_check(BUFSIZE);
-    uint8_t *buf3 = malloc_with_check(BUFSIZE);
-    size_t msglen2, msglen3;
-    alltypes_static_AllTypes *msg1 = malloc_with_check(sizeof(alltypes_static_AllTypes));
-    alltypes_static_AllTypes *msg2 = malloc_with_check(sizeof(alltypes_static_AllTypes));
-    memset(msg1, 0, sizeof(alltypes_static_AllTypes));
-    memset(msg2, 0, sizeof(alltypes_static_AllTypes));
-    
-    {
-        pb_istream_t stream = pb_istream_from_buffer(buffer, msglen);
-        status = pb_decode(&stream, alltypes_static_AllTypes_fields, msg1);
-        assert(status);
-    }
-    
-    {
-        pb_ostream_t stream = pb_ostream_from_buffer(buf2, BUFSIZE);
-        status = pb_encode(&stream, alltypes_static_AllTypes_fields, msg1);
-        assert(status);
-        msglen2 = stream.bytes_written;
-    }
-    
-    {
-        pb_istream_t stream = pb_istream_from_buffer(buf2, msglen2);
-        status = pb_decode(&stream, alltypes_static_AllTypes_fields, msg2);
-        assert(status);
-    }
-    
-    {
-        pb_ostream_t stream = pb_ostream_from_buffer(buf3, BUFSIZE);
-        status = pb_encode(&stream, alltypes_static_AllTypes_fields, msg2);
-        assert(status);
-        msglen3 = stream.bytes_written;
-    }
-    
-    assert(msglen2 == msglen3);
-    assert(memcmp(buf2, buf3, msglen2) == 0);
-    
-    free_with_check(msg1);
-    free_with_check(msg2);
-    free_with_check(buf2);
-    free_with_check(buf3);
-}
-
-/* Do decode -> encode -> decode -> encode roundtrip */
-static void do_pointer_roundtrip(uint8_t *buffer, size_t msglen)
-{
-    bool status;
-    uint8_t *buf2 = malloc_with_check(BUFSIZE);
-    uint8_t *buf3 = malloc_with_check(BUFSIZE);
-    size_t msglen2, msglen3;
-    alltypes_pointer_AllTypes *msg1 = malloc_with_check(sizeof(alltypes_pointer_AllTypes));
-    alltypes_pointer_AllTypes *msg2 = malloc_with_check(sizeof(alltypes_pointer_AllTypes));
-    memset(msg1, 0, sizeof(alltypes_pointer_AllTypes));
-    memset(msg2, 0, sizeof(alltypes_pointer_AllTypes));
-    
-    {
-        pb_istream_t stream = pb_istream_from_buffer(buffer, msglen);
-        status = pb_decode(&stream, alltypes_pointer_AllTypes_fields, msg1);
-        assert(status);
-    }
-    
-    {
-        pb_ostream_t stream = pb_ostream_from_buffer(buf2, BUFSIZE);
-        status = pb_encode(&stream, alltypes_pointer_AllTypes_fields, msg1);
-        assert(status);
-        msglen2 = stream.bytes_written;
-    }
-    
-    {
-        pb_istream_t stream = pb_istream_from_buffer(buf2, msglen2);
-        status = pb_decode(&stream, alltypes_pointer_AllTypes_fields, msg2);
-        assert(status);
-    }
-    
-    {
-        pb_ostream_t stream = pb_ostream_from_buffer(buf3, BUFSIZE);
-        status = pb_encode(&stream, alltypes_pointer_AllTypes_fields, msg2);
-        assert(status);
-        msglen3 = stream.bytes_written;
-    }
-    
-    assert(msglen2 == msglen3);
-    assert(memcmp(buf2, buf3, msglen2) == 0);
-    
-    pb_release(alltypes_pointer_AllTypes_fields, msg1);
-    pb_release(alltypes_pointer_AllTypes_fields, msg2);
-    free_with_check(msg1);
-    free_with_check(msg2);
-    free_with_check(buf2);
-    free_with_check(buf3);
-}
-
-static void run_iteration()
-{
-    uint8_t *buffer = malloc_with_check(BUFSIZE);
-    size_t msglen;
-    bool status;
-    
-    msglen = fread(buffer, 1, BUFSIZE, stdin);
-
-    status = do_static_decode(buffer, msglen, false);
-    
-    if (status)
-        do_static_roundtrip(buffer, msglen);
-    
-    status = do_pointer_decode(buffer, msglen, false);
-    
-    if (status)
-        do_pointer_roundtrip(buffer, msglen);
-    
-    free_with_check(buffer);
-}
-
-int main(int argc, char **argv)
-{
-    run_iteration();
-    
-    return 0;
-}
-
diff --git a/tests/fuzztest/fuzztest.c b/tests/fuzztest/fuzztest.c
index 7e9ddb7..93065ae 100644
--- a/tests/fuzztest/fuzztest.c
+++ b/tests/fuzztest/fuzztest.c
@@ -8,11 +8,17 @@
 #include <stdlib.h>
 #include <string.h>
 #include <assert.h>
-#include <time.h>
 #include <malloc_wrappers.h>
+#include "test_helpers.h"
 #include "alltypes_static.pb.h"
 #include "alltypes_pointer.pb.h"
 
+#ifdef __AVR__
+#define BUFSIZE 2048
+#else
+#define BUFSIZE 4096
+#endif
+
 static uint64_t random_seed;
 
 /* Uses xorshift64 here instead of rand() for both speed and
@@ -198,8 +204,6 @@
     }
 }
 
-#define BUFSIZE 4096
-
 static bool do_static_encode(uint8_t *buffer, size_t *msglen)
 {
     pb_ostream_t stream;
@@ -444,19 +448,49 @@
     assert(get_alloc_count() == 0);
 }
 
+static void run_stub()
+{
+    uint8_t *buffer = malloc_with_check(BUFSIZE);
+    size_t msglen;
+    bool status;
+
+    SET_BINARY_MODE(stdin);
+    msglen = fread(buffer, 1, BUFSIZE, stdin);
+
+    status = do_static_decode(buffer, msglen, false);
+
+    if (status)
+        do_static_roundtrip(buffer, msglen);
+
+    status = do_pointer_decode(buffer, msglen, false);
+
+    if (status)
+        do_pointer_roundtrip(buffer, msglen);
+
+    free_with_check(buffer);
+}
+
 int main(int argc, char **argv)
 {
     int i;
     int iterations;
 
-    random_seed = atol(argv[1]);
-    iterations = atol(argv[2]);
-    if (iterations == 0) iterations = 10000;
-    fprintf(stderr, "Random seed: %u, iterations: %d\n", (unsigned)random_seed, iterations);
-    
-    for (i = 0; i < iterations; i++)
+    if (argc >= 2)
     {
-        run_iteration();
+        /* Run in stand-alone mode */
+        random_seed = atol(argv[1]);
+        iterations = (argc >= 3) ? atol(argv[2]) : 10000;
+        fprintf(stderr, "Random seed: %u, iterations: %d\n", (unsigned)random_seed, iterations);
+
+        for (i = 0; i < iterations; i++)
+        {
+            run_iteration();
+        }
+    }
+    else
+    {
+        /* Run as a stub for afl-fuzz and similar */
+        run_stub();
     }
     
     return 0;
diff --git a/tests/fuzztest/generate_message.c b/tests/fuzztest/generate_message.c
index 73959fe..4c21a02 100644
--- a/tests/fuzztest/generate_message.c
+++ b/tests/fuzztest/generate_message.c
@@ -8,7 +8,6 @@
 #include <stdlib.h>
 #include <string.h>
 #include <assert.h>
-#include <time.h>
 #include "alltypes_static.pb.h"
 
 static uint64_t random_seed;
@@ -66,7 +65,7 @@
 static void generate_message()
 {
     alltypes_static_AllTypes msg;
-    uint8_t buf[8192];
+    uint8_t buf[4096];
     pb_ostream_t stream = {0};
     
     do {
@@ -83,16 +82,15 @@
 
 int main(int argc, char **argv)
 {
-    if (argc > 1)
+    if (argc < 2)
     {
-        random_seed = atol(argv[1]);
+        fprintf(stderr, "Usage: generate_message <seed>\n");
+        return 1;
     }
-    else
-    {
-        random_seed = time(NULL);
-    }
-    
-    fprintf(stderr, "Random seed: %llu\n", (long long unsigned)random_seed);
+
+    random_seed = atol(argv[1]);
+
+    fprintf(stderr, "Random seed: %u\n", (unsigned)random_seed);
     
     generate_message();
     
diff --git a/tests/fuzztest/run_radamsa.sh b/tests/fuzztest/run_radamsa.sh
deleted file mode 100755
index 52cd40a..0000000
--- a/tests/fuzztest/run_radamsa.sh
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/bin/bash
-
-TMP=`tempfile`
-
-echo $TMP
-while true
-do
-   radamsa sample_data/* > $TMP
-   $1 < $TMP
-   test $? -gt 127 && break
-done
- 
diff --git a/tests/site_scons/site_tools/nanopb.py b/tests/site_scons/site_tools/nanopb.py
index 409d86c..f0626c6 100644
--- a/tests/site_scons/site_tools/nanopb.py
+++ b/tests/site_scons/site_tools/nanopb.py
@@ -64,6 +64,11 @@
         # Use protoc bundled with binary package
         return env['ESCAPE'](p1)
 
+    p = os.path.join(n, 'generator', 'protoc')
+    if os.path.exists(p):
+        # Use the grcpio-tools protoc wrapper
+        return env['ESCAPE'](p)
+
     p = env.WhereIs('protoc')
     if p:
         # Use protoc from path