diff --git a/scripts/base/init-bare.zeek b/scripts/base/init-bare.zeek index e80262b125..a6a3154f68 100644 --- a/scripts/base/init-bare.zeek +++ b/scripts/base/init-bare.zeek @@ -579,6 +579,21 @@ const io_poll_interval_live = 10 &redef; ## while testing, but should be used sparingly. const running_under_test: bool = F &redef; +module EventMetadata; + +export { + ## Enum type for metadata identifiers. + type ID: enum { + NETWORK_TIMESTAMP = 1, + }; + + ## A event metadata entry. + type Entry: record { + id: EventMetadata::ID; ##< The registered :zeek:see:`EventMetadata::ID` value. + val: any; ##< The value. Its type matches what was passed to :zeek:see:`EventMetadata::register`. + }; +} + module FTP; export { @@ -751,6 +766,9 @@ module GLOBAL; ## directly and then remove this alias. type EncapsulatingConnVector: vector of Tunnel::EncapsulatingConn; +## A type alias for event metadata. +type event_metadata_vec: vector of EventMetadata::Entry; + ## Statistics about a :zeek:type:`connection` endpoint. ## ## .. zeek:see:: connection diff --git a/src/Event.cc b/src/Event.cc index d113e475cf..8f9cc76315 100644 --- a/src/Event.cc +++ b/src/Event.cc @@ -3,6 +3,7 @@ #include "zeek/Event.h" #include "zeek/Desc.h" +#include "zeek/EventRegistry.h" #include "zeek/Trigger.h" #include "zeek/Val.h" #include "zeek/iosource/Manager.h" @@ -182,6 +183,19 @@ void EventMgr::Process() { // and had the opportunity to spawn new events. } -void EventMgr::InitPostScript() { iosource_mgr->Register(this, true, false); } +void EventMgr::InitPostScript() { + // Check if expected types and identifiers are available. + const auto& et = zeek::id::find_type("EventMetadata::ID"); + if ( ! et ) + zeek::reporter->FatalError("Failed to find EventMetadata::ID"); + const auto& net_ts_val = et->GetEnumVal(et->Lookup("EventMetadata::NETWORK_TIMESTAMP")); + if ( ! net_ts_val ) + zeek::reporter->FatalError("Failed to lookup EventMetadata::NETWORK_TIMESTAMP"); + + if ( ! zeek::event_registry->RegisterMetadata(net_ts_val, zeek::base_type(zeek::TYPE_TIME)) ) + zeek::reporter->FatalError("Failed to register NETWORK_TIMESTAMP metadata"); + + iosource_mgr->Register(this, true, false); +} } // namespace zeek diff --git a/src/EventRegistry.cc b/src/EventRegistry.cc index 0585752af1..ee52e5e62b 100644 --- a/src/EventRegistry.cc +++ b/src/EventRegistry.cc @@ -3,11 +3,16 @@ #include "zeek/EventRegistry.h" #include +#include +#include "zeek/Desc.h" #include "zeek/EventHandler.h" #include "zeek/Func.h" #include "zeek/RE.h" #include "zeek/Reporter.h" +#include "zeek/Traverse.h" +#include "zeek/TraverseTypes.h" +#include "zeek/Type.h" namespace zeek { @@ -164,4 +169,67 @@ void EventGroup::Disable() { void EventGroup::AddFunc(detail::ScriptFuncPtr f) { funcs.insert(f); } +namespace { + +class EventMetadataTypeRejector : public detail::TraversalCallback { +public: + detail::TraversalCode PreType(const Type* t) override { + if ( visited.count(t) > 0 ) + return detail::TC_ABORTSTMT; + + visited.insert(t); + + if ( reject.count(t->Tag()) ) + rejected.push_back(t); + + return detail::TC_CONTINUE; + }; + + std::set visited; + std::vector rejected; + + std::set reject = {TYPE_ANY, TYPE_FUNC, TYPE_FILE, TYPE_OPAQUE}; +}; + +} // namespace + +bool EventRegistry::RegisterMetadata(EnumValPtr id, TypePtr type) { + static const auto& metadata_id_type = id::find_type("EventMetadata::ID"); + + if ( metadata_id_type != id->GetType() ) + return false; + + auto id_int = id->Get(); + if ( id_int < 0 ) { + zeek::reporter->InternalError("Negative enum value %s: %" PRId64, obj_desc_short(id.get()).c_str(), id_int); + } + + zeek_uint_t id_uint = static_cast(id_int); + + if ( auto it = event_metadata_types.find(id_uint); it != event_metadata_types.end() ) + return same_type(it->second.Type(), type); + + EventMetadataTypeRejector cb; + type->Traverse(&cb); + + if ( cb.rejected.size() > 0 ) + return false; + + event_metadata_types.insert({id_uint, EventMetadataDescriptor{id_uint, std::move(id), std::move(type)}}); + + return true; +} + +const EventMetadataDescriptor* EventRegistry::LookupMetadata(zeek_uint_t id) const { + const auto it = event_metadata_types.find(id); + if ( it == event_metadata_types.end() ) + return nullptr; + + if ( it->second.Id() != id ) { + zeek::reporter->InternalError("inconsistent metadata descriptor: %" PRIu64 " vs %" PRId64, it->second.Id(), id); + } + + return &(it->second); +} + } // namespace zeek diff --git a/src/EventRegistry.h b/src/EventRegistry.h index 3db64960ea..d4bfb5aa30 100644 --- a/src/EventRegistry.h +++ b/src/EventRegistry.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -22,18 +23,55 @@ enum class EventGroupKind { Module, }; +class EnumVal; class EventGroup; class EventHandler; class EventHandlerPtr; class RE_Matcher; +class RecordVal; +class Type; +using EnumValPtr = IntrusivePtr; using EventGroupPtr = std::shared_ptr; +using RecordValPtr = IntrusivePtr; +using TypePtr = IntrusivePtr; namespace detail { class ScriptFunc; using ScriptFuncPtr = zeek::IntrusivePtr; + +/** + * Well-known event metadata identifiers. + */ +enum class MetadataType : uint8_t { + NetworkTimestamp = 1, +}; + } // namespace detail +/** + * Descriptor for event metadata. + * + * Event metadata is registered via @ref EventRegistry::RegisterMetadata. The descriptor + * holds the metadata identifier and registered type. For the identifier, + * *id* is the unsigned int representation, while *id_val* holds the + * script-layer zeek::EnumVal. + */ +class EventMetadataDescriptor { +public: + EventMetadataDescriptor(zeek_uint_t id, EnumValPtr id_val, TypePtr type) + : id(id), id_val(std::move(id_val)), type(std::move(type)) {} + + zeek_uint_t Id() const { return id; } + const EnumValPtr& IdVal() const { return id_val; } + const TypePtr& Type() const { return type; } + +private: + zeek_uint_t id; + EnumValPtr id_val; + TypePtr type; +}; + // The registry keeps track of all events that we provide or handle. class EventRegistry final { public: @@ -98,6 +136,23 @@ public: */ EventGroupPtr LookupGroup(EventGroupKind kind, std::string_view name); + /** + * Register a script-layer metadata identifier *id* with type *type*. + * + * @param id The script-level ``EventMetadata::ID`` enum value. + * @param type The type to expect for the given metadata identifier. + */ + bool RegisterMetadata(EnumValPtr id, TypePtr type); + + /** + * Lookup the MetadataDescriptor for metadata identifier *id* + * + * @param id The metadata identifier as unsigned int. + * @return A pointer to a MetadataDescriptor or nullptr. + */ + const EventMetadataDescriptor* LookupMetadata(zeek_uint_t id) const; + + private: std::map, std::less<>> handlers; // Tracks whether a given event handler was registered in a @@ -106,6 +161,9 @@ private: // Map event groups identified by kind and name to their instances. std::map, std::shared_ptr, std::less<>> event_groups; + + // Map for event metadata identifier to their descriptors types. + std::unordered_map event_metadata_types; }; /** diff --git a/src/zeek.bif b/src/zeek.bif index f21d9fa065..5807ee80c7 100644 --- a/src/zeek.bif +++ b/src/zeek.bif @@ -401,6 +401,33 @@ function current_event_time%(%): time return zeek::make_intrusive(zeek::event_mgr.CurrentEventTime()); %} +## Register the expected Zeek type for event metadata. +## +## id: The event metadata identifier. +## +## t: A type expression or type alias. The type cannot be ``any``, ``func``, +## ``file``, ``opaque`` or a composite type containing one of these types. +## +## Returns: true if the registration was successful, false if *id* is +## registered with a different type already, or type is invalid. +## +## .. zeek:see:: EventMetadata::current EventMetadata::current_all +function EventMetadata::register%(id: EventMetadata::ID, t: any%): bool + %{ + const auto& tty = t->GetType(); + + if ( tty->Tag() != zeek::TYPE_TYPE ) + { + zeek::emit_builtin_error("register_event_metadata() expects type"); + return zeek::val_mgr->False(); + } + + EnumValPtr idvp = {zeek::NewRef{}, id->AsEnumVal()}; + + bool r = zeek::event_registry->RegisterMetadata(idvp, tty->AsTypeType()->GetType()); + return zeek::val_mgr->Bool(r); + %} + ## Returns a system environment variable. ## ## var: The name of the variable whose value to request. diff --git a/testing/btest/Baseline/core.event-metadata.register-type-errors/.stderr b/testing/btest/Baseline/core.event-metadata.register-type-errors/.stderr new file mode 100644 index 0000000000..49d861c74c --- /dev/null +++ b/testing/btest/Baseline/core.event-metadata.register-type-errors/.stderr @@ -0,0 +1 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. diff --git a/testing/btest/Baseline/core.event-metadata.register-type-errors/.stdout b/testing/btest/Baseline/core.event-metadata.register-type-errors/.stdout new file mode 100644 index 0000000000..49d861c74c --- /dev/null +++ b/testing/btest/Baseline/core.event-metadata.register-type-errors/.stdout @@ -0,0 +1 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. diff --git a/testing/btest/core/event-metadata/register-type-errors.zeek b/testing/btest/core/event-metadata/register-type-errors.zeek new file mode 100644 index 0000000000..440d7161c8 --- /dev/null +++ b/testing/btest/core/event-metadata/register-type-errors.zeek @@ -0,0 +1,30 @@ +# @TEST-DOC: Verify rejection of certain metadata types. +# +# @TEST-EXEC: unset ZEEK_ALLOW_INIT_ERRORS; zeek -b %INPUT +# @TEST-EXEC: btest-diff .stdout +# @TEST-EXEC: TEST_DIFF_CANONIFIER=$SCRIPTS/diff-remove-abspath btest-diff .stderr + +module App; + +export { + redef enum EventMetadata::ID += { + MY_METADATA = 1000, + MY_TABLE = 1002, + MY_VECTOR = 1003, + }; +} + +type R: record { + f: file; + a: any; + l: function(x: count): bool; +}; + +event zeek_init() + { + assert ! EventMetadata::register(MY_METADATA, any); + assert ! EventMetadata::register(MY_METADATA, table[count] of any); + assert ! EventMetadata::register(MY_METADATA, table[count] of function(x: count): bool); + assert ! EventMetadata::register(MY_METADATA, R); + assert ! EventMetadata::register(MY_METADATA, vector of R); + }