From 43e3de5c7951d5f7a745e27945315c10280230e7 Mon Sep 17 00:00:00 2001 From: Tim Wojtulewicz Date: Tue, 3 Dec 2024 09:15:26 -0700 Subject: [PATCH] Add interval_as_double argument to control how intervals are converted to JSON --- src/Val.cc | 31 +++++++++++++------ src/Val.h | 5 ++- src/zeek.bif | 8 +++-- .../scripts.base.utils.json-roundtrip/output | 4 +++ .../scripts/base/utils/json-roundtrip.test | 20 +++++++++++- 5 files changed, 54 insertions(+), 14 deletions(-) diff --git a/src/Val.cc b/src/Val.cc index ed7beed968..d62b486464 100644 --- a/src/Val.cc +++ b/src/Val.cc @@ -351,7 +351,7 @@ static bool UsesJSONStringType(const TypePtr& t) { // This is a static method in this file to avoid including rapidjson's headers // in Val.h, because they're huge. static void BuildJSON(json::detail::NullDoubleWriter& writer, Val* val, bool only_loggable = false, - RE_Matcher* re = nullptr, const string& key = "") { + RE_Matcher* re = nullptr, const string& key = "", bool interval_as_double = false) { if ( ! key.empty() ) writer.Key(key); @@ -387,7 +387,6 @@ static void BuildJSON(json::detail::NullDoubleWriter& writer, Val* val, bool onl } case TYPE_PATTERN: - case TYPE_INTERVAL: case TYPE_ADDR: case TYPE_SUBNET: { ODesc d; @@ -397,6 +396,18 @@ static void BuildJSON(json::detail::NullDoubleWriter& writer, Val* val, bool onl break; } + case TYPE_INTERVAL: { + if ( interval_as_double ) + writer.Double(val->AsInterval()); + else { + ODesc d; + d.SetStyle(RAW_STYLE); + val->Describe(&d); + writer.String(reinterpret_cast(d.Bytes()), d.Len()); + } + break; + } + case TYPE_FILE: case TYPE_FUNC: case TYPE_ENUM: @@ -433,11 +444,11 @@ static void BuildJSON(json::detail::NullDoubleWriter& writer, Val* val, bool onl Val* entry_key = lv->Length() == 1 ? lv->Idx(0).get() : lv.get(); if ( tval->GetType()->IsSet() ) - BuildJSON(writer, entry_key, only_loggable, re); + BuildJSON(writer, entry_key, only_loggable, re, "", interval_as_double); else { rapidjson::StringBuffer buffer; json::detail::NullDoubleWriter key_writer(buffer); - BuildJSON(key_writer, entry_key, only_loggable, re); + BuildJSON(key_writer, entry_key, only_loggable, re, "", interval_as_double); string key_str = buffer.GetString(); // Strip the quotes for any type we render as a string. This @@ -446,7 +457,7 @@ static void BuildJSON(json::detail::NullDoubleWriter& writer, Val* val, bool onl if ( UsesJSONStringType(entry_key->GetType()) ) key_str = key_str.substr(1, key_str.length() - 2); - BuildJSON(writer, entry->GetVal().get(), only_loggable, re, key_str); + BuildJSON(writer, entry->GetVal().get(), only_loggable, re, key_str, interval_as_double); } } @@ -481,7 +492,7 @@ static void BuildJSON(json::detail::NullDoubleWriter& writer, Val* val, bool onl else key_str = field_name; - BuildJSON(writer, value.get(), only_loggable, re, key_str); + BuildJSON(writer, value.get(), only_loggable, re, key_str, interval_as_double); } } @@ -495,7 +506,7 @@ static void BuildJSON(json::detail::NullDoubleWriter& writer, Val* val, bool onl auto* lval = val->AsListVal(); size_t size = lval->Length(); for ( size_t i = 0; i < size; i++ ) - BuildJSON(writer, lval->Idx(i).get(), only_loggable, re); + BuildJSON(writer, lval->Idx(i).get(), only_loggable, re, "", interval_as_double); writer.EndArray(); break; @@ -507,7 +518,7 @@ static void BuildJSON(json::detail::NullDoubleWriter& writer, Val* val, bool onl auto* vval = val->AsVectorVal(); size_t size = vval->SizeVal()->AsCount(); for ( size_t i = 0; i < size; i++ ) - BuildJSON(writer, vval->ValAt(i).get(), only_loggable, re); + BuildJSON(writer, vval->ValAt(i).get(), only_loggable, re, "", interval_as_double); writer.EndArray(); break; @@ -528,11 +539,11 @@ static void BuildJSON(json::detail::NullDoubleWriter& writer, Val* val, bool onl } } -StringValPtr Val::ToJSON(bool only_loggable, RE_Matcher* re) { +StringValPtr Val::ToJSON(bool only_loggable, RE_Matcher* re, bool interval_as_double) { rapidjson::StringBuffer buffer; json::detail::NullDoubleWriter writer(buffer); - BuildJSON(writer, this, only_loggable, re, ""); + BuildJSON(writer, this, only_loggable, re, "", interval_as_double); return make_intrusive(buffer.GetString()); } diff --git a/src/Val.h b/src/Val.h index 8d5d8f3d52..bc6de01ddd 100644 --- a/src/Val.h +++ b/src/Val.h @@ -248,9 +248,12 @@ public: * first match on any record field name in the resulting output. See the * to_json() BiF for context. * + * @param interval_as_double If true, interval values will be written as + * doubles instead of the broken-out version with units. + * * @return JSON data representing the Val. */ - StringValPtr ToJSON(bool only_loggable = false, RE_Matcher* re = nullptr); + StringValPtr ToJSON(bool only_loggable = false, RE_Matcher* re = nullptr, bool interval_as_double = false); template T As() { diff --git a/src/zeek.bif b/src/zeek.bif index 6cc5276e48..ee14c0ddce 100644 --- a/src/zeek.bif +++ b/src/zeek.bif @@ -5103,14 +5103,18 @@ function anonymize_addr%(a: addr, cl: IPAddrAnonymizationClass%): addr ## rendered name. The default pattern strips a leading ## underscore. ## +## interval_as_double: If T, interval values will be logged as doubles +## instead of the broken-out version with units as strings. +## ## returns: a JSON formatted string. ## ## .. zeek:see:: fmt cat cat_sep string_cat print_raw from_json -function to_json%(val: any, only_loggable: bool &default=F, field_escape_pattern: pattern &default=/^_/%): string +function to_json%(val: any, only_loggable: bool &default=F, field_escape_pattern: pattern &default=/^_/, interval_as_double: bool &default=F%): string %{ - return val->ToJSON(only_loggable, field_escape_pattern); + return val->ToJSON(only_loggable, field_escape_pattern, interval_as_double); %} + ## A function to convert a JSON string into Zeek values of a given type. ## ## Implicit conversion from JSON to Zeek types is implemented for: diff --git a/testing/btest/Baseline/scripts.base.utils.json-roundtrip/output b/testing/btest/Baseline/scripts.base.utils.json-roundtrip/output index 83d4c6b4fa..89017ab79a 100644 --- a/testing/btest/Baseline/scripts.base.utils.json-roundtrip/output +++ b/testing/btest/Baseline/scripts.base.utils.json-roundtrip/output @@ -19,3 +19,7 @@ s, T re, T su, T se, T + +Valid conversion of IntervalOnly: 1 + +it, T diff --git a/testing/btest/scripts/base/utils/json-roundtrip.test b/testing/btest/scripts/base/utils/json-roundtrip.test index 19a5122052..4caa06c05e 100644 --- a/testing/btest/scripts/base/utils/json-roundtrip.test +++ b/testing/btest/scripts/base/utils/json-roundtrip.test @@ -30,6 +30,10 @@ type Foo: record { tbl: table[addr, port] of string; }; +type IntervalOnly: record { + it: interval; +}; + event zeek_init() { @@ -55,7 +59,7 @@ event zeek_init() local f_json = to_json(f); local f2 = from_json(f_json, Foo); - print fmt("Valid conversion of Foo: %d", f2$valid); + print fmt("Valid conversion of Foo: %d", f2$valid); print ""; local f2_v : Foo = f2$v; @@ -81,4 +85,18 @@ event zeek_init() # TODO: direct comparisons of tables isn't allowed. This will have to wait. # print f$tbl == f2_v$tbl; + + local io: IntervalOnly; + io$it = double_to_interval(2*24*3600 + 2*3600 + 2*60 + 2*1.0 + 2*0.1 + 2*0.0001); + + # Test round-trip conversion of intervals as doubles. + local io_json = to_json(io, F, /^_/, T); + local io2 = from_json(io_json, IntervalOnly); + + print ""; + print fmt("Valid conversion of IntervalOnly: %d", f2$valid); + print ""; + + local io2_v : IntervalOnly = io2$v; + print "it", io$it == io2_v$it; }