diff --git a/src/threading/Formatter.cc b/src/threading/Formatter.cc index 395a7fefa6..5fa8ab6fa9 100644 --- a/src/threading/Formatter.cc +++ b/src/threading/Formatter.cc @@ -111,3 +111,14 @@ string Formatter::Render(double d) return buf; } +string Formatter::Render(TransportProto proto) + { + if ( proto == TRANSPORT_UDP ) + return "udp"; + else if ( proto == TRANSPORT_TCP ) + return "tcp"; + else if ( proto == TRANSPORT_ICMP ) + return "icmp"; + else + return "unknown"; + } diff --git a/src/threading/Formatter.h b/src/threading/Formatter.h index c564f3c945..53ca22254b 100644 --- a/src/threading/Formatter.h +++ b/src/threading/Formatter.h @@ -112,6 +112,17 @@ public: */ static string Render(double d); + /** + * Convert a transport protocol into a string. + * + * This is a helper function that formatter implementations may use. + * + * @param proto The transport protocol. + * + * @return An ASCII representation of the protocol. + */ + static string Render(TransportProto proto); + /** * Convert a string into a TransportProto. The string must be one of * \c tcp, \c udp, \c icmp, or \c unknown. diff --git a/src/threading/formatters/JSON.cc b/src/threading/formatters/JSON.cc index a324a08530..c7a6f574a9 100644 --- a/src/threading/formatters/JSON.cc +++ b/src/threading/formatters/JSON.cc @@ -11,7 +11,7 @@ #include #include -#include "./JSON.h" +#include "JSON.h" using namespace threading::formatter; @@ -25,80 +25,85 @@ JSON::~JSON() } bool JSON::Describe(ODesc* desc, int num_fields, const Field* const * fields, - Value** vals) const + Value** vals) const { - if ( surrounding_braces ) - desc->AddRaw("{"); + ZeekJson j = ZeekJson::object(); for ( int i = 0; i < num_fields; i++ ) { - const u_char* bytes = desc->Bytes(); - int len = desc->Len(); + if ( vals[i]->present ) + { + ZeekJson new_entry = BuildJSON(vals[i]); + if ( new_entry.is_null() ) + return false; - if ( i > 0 && - len > 0 && - bytes[len-1] != ',' && - bytes[len-1] != '{' && - bytes[len-1] != '[' && - vals[i]->present ) - desc->AddRaw(","); - - if ( ! Describe(desc, vals[i], fields[i]->name) ) - return false; + j[fields[i]->name] = new_entry; + } } - if ( surrounding_braces ) - desc->AddRaw("}"); + desc->Add(j.dump()); return true; } bool JSON::Describe(ODesc* desc, Value* val, const string& name) const { + if ( desc->IsBinary() ) + { + GetThread()->Error("json formatter: binary format not supported"); + return false; + } + if ( ! val->present ) return true; - if ( name.size() ) - { - desc->AddRaw("\"", 1); - desc->Add(name); - desc->AddRaw("\":", 2); - } + ZeekJson j = BuildJSON(val, name); + if ( j.is_null() ) + return false; + desc->Add(j.dump()); + return true; + } + +threading::Value* JSON::ParseValue(const string& s, const string& name, TypeTag type, TypeTag subtype) const + { + GetThread()->Error("JSON formatter does not support parsing yet."); + return NULL; + } + +ZeekJson JSON::BuildJSON(Value* val, const string& name) const + { + ZeekJson j; switch ( val->type ) { case TYPE_BOOL: - desc->AddRaw(val->val.int_val == 0 ? "false" : "true"); + j = val->val.int_val != 0; break; case TYPE_INT: - desc->Add(val->val.int_val); + j = val->val.int_val; break; case TYPE_COUNT: case TYPE_COUNTER: - desc->Add(val->val.uint_val); + j = val->val.uint_val; break; case TYPE_PORT: - desc->Add(val->val.port_val.port); + j = val->val.port_val.port; break; case TYPE_SUBNET: - desc->AddRaw("\"", 1); - desc->Add(Render(val->val.subnet_val)); - desc->AddRaw("\"", 1); + j = Formatter::Render(val->val.subnet_val); break; case TYPE_ADDR: - desc->AddRaw("\"", 1); - desc->Add(Render(val->val.addr_val)); - desc->AddRaw("\"", 1); + j = Formatter::Render(val->val.addr_val); break; case TYPE_DOUBLE: case TYPE_INTERVAL: - desc->Add(val->val.double_val); + j = val->val.double_val; break; case TYPE_TIME: @@ -110,15 +115,13 @@ bool JSON::Describe(ODesc* desc, Value* val, const string& name) const time_t the_time = time_t(floor(val->val.double_val)); struct tm t; - desc->AddRaw("\"", 1); - if ( ! gmtime_r(&the_time, &t) || - ! strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%S", &t) ) + ! strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%S", &t) ) { GetThread()->Error(GetThread()->Fmt("json formatter: failure getting time: (%lf)", val->val.double_val)); // This was a failure, doesn't really matter what gets put here // but it should probably stand out... - desc->Add("2000-01-01T00:00:00.000000"); + j = "2000-01-01T00:00:00.000000"; } else { @@ -129,20 +132,17 @@ bool JSON::Describe(ODesc* desc, Value* val, const string& name) const frac += 1; snprintf(buffer2, sizeof(buffer2), "%s.%06.0fZ", buffer, fabs(frac) * 1000000); - desc->Add(buffer2); + j = buffer2; } - - desc->AddRaw("\"", 1); } else if ( timestamps == TS_EPOCH ) - desc->Add(val->val.double_val); + j = val->val.double_val; else if ( timestamps == TS_MILLIS ) { // ElasticSearch uses milliseconds for timestamps - uint64_t ts = (uint64_t) (val->val.double_val * 1000); - desc->Add(ts); + j = (uint64_t) (val->val.double_val * 1000); } break; @@ -153,74 +153,40 @@ bool JSON::Describe(ODesc* desc, Value* val, const string& name) const case TYPE_FILE: case TYPE_FUNC: { - desc->AddRaw("\"", 1); - - for ( int i = 0; i < val->val.string_val.length; ++i ) - { - char c = val->val.string_val.data[i]; - - // 2byte Unicode escape special characters. - if ( c < 32 || c > 126 || c == '\n' || c == '"' || c == '\'' || c == '\\' || c == '&' ) - { - desc->AddRaw("\\u00", 4); - char hex[2] = {'0', '0'}; - bytetohex(c, hex); - desc->AddRaw(hex, 1); - desc->AddRaw(hex + 1, 1); - } - else - desc->AddRaw(&c, 1); - } - - desc->AddRaw("\"", 1); + j = json_escape_utf8(string(val->val.string_val.data, val->val.string_val.length)); break; } case TYPE_TABLE: { - desc->AddRaw("[", 1); + j = ZeekJson::array(); - for ( int j = 0; j < val->val.set_val.size; j++ ) - { - if ( j > 0 ) - desc->AddRaw(",", 1); + for ( int idx = 0; idx < val->val.set_val.size; idx++ ) + j.push_back(BuildJSON(val->val.set_val.vals[idx])); - Describe(desc, val->val.set_val.vals[j]); - } - - desc->AddRaw("]", 1); break; } case TYPE_VECTOR: { - desc->AddRaw("[", 1); + j = ZeekJson::array(); - for ( int j = 0; j < val->val.vector_val.size; j++ ) - { - if ( j > 0 ) - desc->AddRaw(",", 1); - Describe(desc, val->val.vector_val.vals[j]); - } + for ( int idx = 0; idx < val->val.vector_val.size; idx++ ) + j.push_back(BuildJSON(val->val.vector_val.vals[idx])); - desc->AddRaw("]", 1); break; } default: - return false; + break; } - return true; - } + if ( ! name.empty() && ! j.is_null() ) + { + ZeekJson j2 = ZeekJson::object(); + j2[name] = j; + return j2; + } -threading::Value* JSON::ParseValue(const string& s, const string& name, TypeTag type, TypeTag subtype) const - { - GetThread()->Error("JSON formatter does not support parsing yet."); - return NULL; - } - -void JSON::SurroundingBraces(bool use_braces) - { - surrounding_braces = use_braces; + return j; } diff --git a/src/threading/formatters/JSON.h b/src/threading/formatters/JSON.h index 4984f74067..8b9ab1ed6e 100644 --- a/src/threading/formatters/JSON.h +++ b/src/threading/formatters/JSON.h @@ -4,9 +4,19 @@ #define THREADING_FORMATTERS_JSON_H #include "../Formatter.h" +#include "3rdparty/json.hpp" +#include "3rdparty/fifo_map.hpp" + namespace threading { namespace formatter { +// Define a class for use with the json library that orders the keys in the same order that +// they were inserted. By default, the json library orders them alphabetically and we don't +// want it like that. +template +using json_fifo_map = nlohmann::fifo_map, A>; +using ZeekJson = nlohmann::basic_json; + /** * A thread-safe class for converting values into a JSON representation * and vice versa. @@ -27,9 +37,10 @@ public: threading::Value** vals) const override; threading::Value* ParseValue(const string& s, const string& name, TypeTag type, TypeTag subtype = TYPE_ERROR) const override; - void SurroundingBraces(bool use_braces); - private: + + ZeekJson BuildJSON(Value* val, const string& name = "") const; + TimeFormat timestamps; bool surrounding_braces; };