Add a simple raw_decode utility for help in debugging.
diff --git a/tests/raw_decode/SConscript b/tests/raw_decode/SConscript
new file mode 100644
index 0000000..b18c9ae
--- /dev/null
+++ b/tests/raw_decode/SConscript
@@ -0,0 +1,10 @@
+# This builds a simple utility that decodes a binary protobuf message.
+# It is similar to protoc --decode_raw, except it produces useful information
+# even for corrupted messages.
+
+Import("env")
+
+dec = env.Program(["raw_decode.c", "$COMMON/pb_decode.o", "$COMMON/pb_common.o"])
+
+env.RunTest([dec, "$BUILD/alltypes/encode_alltypes.output"])
+
diff --git a/tests/raw_decode/raw_decode.c b/tests/raw_decode/raw_decode.c
new file mode 100644
index 0000000..4dd76d8
--- /dev/null
+++ b/tests/raw_decode/raw_decode.c
@@ -0,0 +1,194 @@
+/* A small tool that decodes a raw binary message, while providing useful
+ * info on corrupted messages also. */
+
+/* Define _ISOC99_SOURCE to get snprintf() even though otherwise in ansi-C mode */
+#define _ISOC99_SOURCE 1
+
+#include <stdio.h>
+#include <string.h>
+#include <pb_decode.h>
+#include "test_helpers.h"
+
+#define HISTORY_LEN 32
+static pb_byte_t g_history[HISTORY_LEN];
+static int g_position;
+
+/* This binds the pb_istream_t to stdin and logs the most recent bytes read. */
+bool callback(pb_istream_t *stream, uint8_t *buf, size_t count)
+{
+ FILE *file = (FILE*)stream->state;
+ size_t len = fread(buf, 1, count, file);
+
+ if (len < HISTORY_LEN)
+ {
+ memmove(g_history, g_history + len, HISTORY_LEN - len);
+ memcpy(g_history + HISTORY_LEN - len, buf, len);
+ }
+ else
+ {
+ memcpy(g_history, buf + len - HISTORY_LEN, HISTORY_LEN);
+ }
+
+ g_position += len;
+
+ if (len == count)
+ {
+ return true;
+ }
+ else
+ {
+ stream->bytes_left = 0;
+ return false;
+ }
+}
+
+void print_history(int position)
+{
+ int i;
+
+ if (position < g_position - HISTORY_LEN)
+ position = g_position - HISTORY_LEN;
+
+ printf("LATEST BYTES READ (%d to %d): ", position, g_position);
+
+ for (i = HISTORY_LEN - (g_position - position); i < HISTORY_LEN; i++)
+ {
+ printf("%02x ", g_history[i]);
+ }
+
+ printf("\n");
+}
+
+bool raw_decode(pb_istream_t *stream, const char *indent)
+{
+ const char *wiretypes[8] = {"VARINT", "64BIT", "STRING", "SGRP", "EGRP", "32BIT", "????", "????"};
+
+ while (stream->bytes_left)
+ {
+ uint32_t tag;
+ pb_wire_type_t wire_type;
+ bool eof;
+ int position = g_position;
+
+ if (!pb_decode_tag(stream, &wire_type, &tag, &eof))
+ {
+ if (eof)
+ {
+ break;
+ }
+ else
+ {
+ printf("ERROR: Failed to parse tag: %s\n", PB_GET_ERROR(stream));
+ print_history(position);
+ return false;
+ }
+ }
+
+ if (tag == 0)
+ {
+ printf("%sterminating on zero tag\n", indent);
+ return true;
+ }
+
+ printf("%sAt %d: field tag %d, wire type %d (%s)",
+ indent, position, (int)tag, wire_type, wiretypes[wire_type]);
+
+ if (wire_type == PB_WT_VARINT)
+ {
+ uint64_t value;
+ position = g_position;
+ if (!pb_decode_varint(stream, &value))
+ {
+ printf("\n%sERROR: Failed to parse varint: %s\n", indent, PB_GET_ERROR(stream));
+ print_history(position);
+ return false;
+ }
+
+ printf(", varint value (%d bytes): %llu\n",
+ g_position - position, (unsigned long long)value);
+ }
+ else if (wire_type == PB_WT_64BIT)
+ {
+ uint64_t value;
+ position = g_position;
+ if (!pb_decode_fixed64(stream, &value))
+ {
+ printf("\n%sERROR: Failed to parse fixed64: %s\n", indent, PB_GET_ERROR(stream));
+ print_history(position);
+ return false;
+ }
+
+ printf(", fixed64 value (%d bytes): 0x%016llx\n",
+ g_position - position, (unsigned long long)value);
+ }
+ else if (wire_type == PB_WT_32BIT)
+ {
+ uint32_t value;
+ position = g_position;
+ if (!pb_decode_fixed32(stream, &value))
+ {
+ printf("\n%sERROR: Failed to parse fixed32: %s\n", indent, PB_GET_ERROR(stream));
+ print_history(position);
+ return false;
+ }
+
+ printf(", fixed32 value (%d bytes): 0x%08lx\n",
+ g_position - position, (unsigned long)value);
+ }
+ else if (wire_type == PB_WT_STRING)
+ {
+ pb_istream_t substream;
+ position = g_position;
+ if (!pb_make_string_substream(stream, &substream))
+ {
+ printf("ERROR: Failed to parse string length: %s\n", PB_GET_ERROR(stream));
+ print_history(position);
+ return false;
+ }
+ else
+ {
+
+ if (substream.bytes_left == 0)
+ {
+ printf(", empty string\n");
+ }
+ else
+ {
+ char prefix[8];
+ snprintf(prefix, sizeof(prefix), "f%d> ", (int)tag);
+
+ printf(", string len %d bytes, attempting recursive decode\n",
+ (int)substream.bytes_left);
+
+ if (!raw_decode(&substream, prefix))
+ {
+ printf("%sfield %d: recursive decode failed, continuing with upper level\n\n",
+ indent, (int)tag);
+ }
+
+ pb_close_string_substream(stream, &substream);
+ }
+ }
+ }
+ else
+ {
+ printf("\n");
+ }
+ }
+
+ return true;
+}
+
+int main()
+{
+ pb_istream_t stream = {&callback, NULL, SIZE_MAX};
+ stream.state = stdin;
+ SET_BINARY_MODE(stdin);
+
+ if (!raw_decode(&stream, ""))
+ {
+ return 1;
+ } else {
+ return 0;
+ }
+}