Add flag to aos_dump and log_cat to print integers in hex format.

Define new --use_hex flag on aos_dump and log_cat applications that
controls how integers are formatted in the output.
Add use_hex field to JsonOptions struct used by FlatbufferToJson.
Update internal helpers used by FlatbufferToJson to act on new tag.

Change-Id: I26dea2cc37de3af4a1e10852e0df8ff93a696802
Signed-off-by: Austin Schuh <austin.schuh@bluerivertech.com>
diff --git a/aos/aos_dump.cc b/aos/aos_dump.cc
index 2fe4955..86f4305 100644
--- a/aos/aos_dump.cc
+++ b/aos/aos_dump.cc
@@ -26,6 +26,7 @@
 DEFINE_int32(timeout, -1,
              "The max time in milliseconds to wait for messages before "
              "exiting.  -1 means forever, 0 means don't wait.");
+DEFINE_bool(use_hex, false, "Are integers in the messages printed in hex notation.");
 
 namespace {
 
@@ -48,7 +49,7 @@
   aos::FlatbufferToJson(
       builder, channel->schema(), static_cast<const uint8_t *>(context.data),
       {FLAGS_pretty, static_cast<size_t>(FLAGS_max_vector_size),
-       FLAGS_pretty_max});
+       FLAGS_pretty_max, FLAGS_use_hex});
 
   if (FLAGS_print_timestamps) {
     if (context.monotonic_remote_time != context.monotonic_event_time) {
diff --git a/aos/events/logging/log_cat.cc b/aos/events/logging/log_cat.cc
index 3e1dd1e..1333de6 100644
--- a/aos/events/logging/log_cat.cc
+++ b/aos/events/logging/log_cat.cc
@@ -54,6 +54,7 @@
 DEFINE_double(monotonic_end_time, 0.0,
               "If set, only print messages sent at or before this many seconds "
               "after epoch.");
+DEFINE_bool(use_hex, false, "Are integers in the messages printed in hex notation.");
 
 using aos::monotonic_clock;
 namespace chrono = std::chrono;
@@ -96,7 +97,10 @@
 
   aos::FlatbufferToJson(
       builder, channel->schema(), static_cast<const uint8_t *>(context.data),
-      {FLAGS_pretty, static_cast<size_t>(FLAGS_max_vector_size)});
+      {.multi_line = FLAGS_pretty,
+       .max_vector_size = static_cast<size_t>(FLAGS_max_vector_size),
+       .max_multi_line = false,
+       .use_hex = FLAGS_use_hex});
 
   if (FLAGS_json) {
     std::cout << "{";
diff --git a/aos/fast_string_builder.h b/aos/fast_string_builder.h
index 9e111ff..aa6b619 100644
--- a/aos/fast_string_builder.h
+++ b/aos/fast_string_builder.h
@@ -37,7 +37,7 @@
 
   // Append integer to result, converted to string representation.
   template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
-  void AppendInt(T val);
+  void AppendInt(T val, bool use_hex = false);
 
   void Append(std::string_view);
 
@@ -70,11 +70,18 @@
 };
 
 template <typename T, typename>
-void FastStringBuilder::AppendInt(T val) {
-  std::size_t index = str_.size();
-  Resize(absl::numbers_internal::kFastToBufferSize);
-  char *end = absl::numbers_internal::FastIntToBuffer(val, str_.data() + index);
-  str_.resize(end - str_.data());
+void FastStringBuilder::AppendInt(T val, bool use_hex) {
+  if (use_hex) {
+    // This is not fast like the decimal path, but hex should be used in limited cases.
+    std::stringstream ss;
+    ss << std::hex << val;
+    str_ += ss.str();
+  } else {
+    std::size_t index = str_.size();
+    Resize(absl::numbers_internal::kFastToBufferSize);
+    char *end = absl::numbers_internal::FastIntToBuffer(val, str_.data() + index);
+    str_.resize(end - str_.data());
+  }
 }
 
 }  // namespace aos
diff --git a/aos/flatbuffer_introspection.cc b/aos/flatbuffer_introspection.cc
index b442af8..ddac795 100644
--- a/aos/flatbuffer_introspection.cc
+++ b/aos/flatbuffer_introspection.cc
@@ -11,34 +11,47 @@
 using reflection::BaseType;
 
 void IntToString(int64_t val, reflection::BaseType type,
-                 FastStringBuilder *out) {
+                 FastStringBuilder *out, bool use_hex) {
+  // For 1-byte types in hex mode, we need to cast to 2 bytes to get the desired output and
+  // not unprintable characters.
+  if (use_hex) {
+    if (BaseType::UByte == type) {
+      out->AppendInt(static_cast<uint16_t>(val), true);
+      return;
+    }
+    if (BaseType::Byte == type) {
+      out->AppendInt(static_cast<int16_t>(val), true);
+      return;
+    }
+  }
+
   switch (type) {
     case BaseType::Bool:
       out->AppendBool(static_cast<bool>(val));
       break;
     case BaseType::UByte:
-      out->AppendInt(static_cast<uint8_t>(val));
+      out->AppendInt(static_cast<uint8_t>(val), use_hex);
       break;
     case BaseType::Byte:
-      out->AppendInt(static_cast<int8_t>(val));
+      out->AppendInt(static_cast<int8_t>(val), use_hex);
       break;
     case BaseType::Short:
-      out->AppendInt(static_cast<int16_t>(val));
+      out->AppendInt(static_cast<int16_t>(val), use_hex);
       break;
     case BaseType::UShort:
-      out->AppendInt(static_cast<uint16_t>(val));
+      out->AppendInt(static_cast<uint16_t>(val), use_hex);
       break;
     case BaseType::Int:
-      out->AppendInt(static_cast<int32_t>(val));
+      out->AppendInt(static_cast<int32_t>(val), use_hex);
       break;
     case BaseType::UInt:
-      out->AppendInt(static_cast<uint32_t>(val));
+      out->AppendInt(static_cast<uint32_t>(val), use_hex);
       break;
     case BaseType::Long:
-      out->AppendInt(static_cast<int64_t>(val));
+      out->AppendInt(static_cast<int64_t>(val), use_hex);
       break;
     case BaseType::ULong:
-      out->AppendInt(static_cast<uint64_t>(val));
+      out->AppendInt(static_cast<uint64_t>(val), use_hex);
       break;
     default:
       out->Append("null");
@@ -84,7 +97,7 @@
 void IntOrEnumToString(
     int64_t val, const reflection::Type *type,
     const flatbuffers::Vector<flatbuffers::Offset<reflection::Enum>> *enums,
-    FastStringBuilder *out) {
+    FastStringBuilder *out, bool use_hex = false) {
   // Check if integer is an enum and print string, otherwise fallback to
   // printing as int.
   if (type->index() > -1 && type->index() < (int32_t)enums->size()) {
@@ -104,9 +117,9 @@
   } else {
     if (type->base_type() == BaseType::Vector ||
         type->base_type() == BaseType::Array) {
-      IntToString(val, type->element(), out);
+      IntToString(val, type->element(), out, use_hex);
     } else {
-      IntToString(val, type->base_type(), out);
+      IntToString(val, type->base_type(), out, use_hex);
     }
   }
 }
@@ -150,7 +163,7 @@
     case BaseType::UInt:
     case BaseType::Long:
     case BaseType::ULong:
-      IntOrEnumToString(GetAnyFieldI(*table, *field), type, enums, out);
+      IntOrEnumToString(GetAnyFieldI(*table, *field), type, enums, out, json_options.use_hex);
       break;
     case BaseType::Float:
     case BaseType::Double:
@@ -228,7 +241,7 @@
           if (flatbuffers::IsInteger(elem_type)) {
             IntOrEnumToString(
                 flatbuffers::GetAnyVectorElemI(vector, elem_type, i), type,
-                enums, out);
+                enums, out, json_options.use_hex);
           } else if (flatbuffers::IsFloat(elem_type)) {
             FloatToString(flatbuffers::GetAnyVectorElemF(vector, elem_type, i),
                           elem_type, out);
diff --git a/aos/json_to_flatbuffer.h b/aos/json_to_flatbuffer.h
index 45920a8..d5e1039 100644
--- a/aos/json_to_flatbuffer.h
+++ b/aos/json_to_flatbuffer.h
@@ -45,6 +45,8 @@
   // more extensive version of multi_line that prints every single field on its
   // own line.
   bool max_multi_line = false;
+  // Will integers be printed in hexadecimal form instead of decimal.
+  bool use_hex = false;
 };
 
 // Converts a flatbuffer into a Json string.