Spicy: Provide runtime API to access Zeek-side globals.

This allows to read Zeek global variables from inside Spicy code. The
main challenge here is supporting all of Zeek's data type in a
type-safe manner.

The most straight-forward API is a set of functions
`get_<type>(<id>)`, where `<type>` is the Zeek-side type
name (e.g., `count`, `string`, `bool`) and `<id>` is the fully scoped
name of the Zeek-side global (e.g., `MyModule::Boolean`). These
functions then return the corresponding Zeek value, converted in an
appropriate Spicy type. Example:

    Zeek:
        module Foo;

        const x: count = 42;
        const y: string = "xxx";

    Spicy:
        import zeek;

        assert zeek::get_count("Foo::x") == 42;
        assert zeek::get_string("Foo::y") == b"xxx"; # returns bytes(!)

For container types, the `get_*` function returns an opaque types that
can be used to access the containers' values. An additional set of
functions `as_<type>` allows converting opaque values of atomic
types to Spicy equivalents. Example:

    Zeek:
        module Foo;

        const s: set[count] = { 1, 2 };
        const t: table[count] of string = { [1] = "One", [2] = "Two" }

    Spicy:

        # Check set membership.
        local set_ = zeek::get_set("Foo::s");
        assert zeek::set_contains(set_, 1) == True

        # Look up table element.
        local table_ = zeek::get_table("Foo::t");
        local value = zeek::table_lookup(t, 1);
        assert zeek::as_string(value) == b"One"

There are also functions for accessing elements of Zeek-side vectors
and records.

If any of these `zeek::*` conversion functions fails (e.g., due to a
global of that name not existing), it will throw an exception.

Design considerations:

    - We support only reading Zeek variables, not writing. This is
      both to simplify the API, and also conceptually to avoid
      offering backdoors into Zeek state that could end up with a very
      tight coupling of Spicy and Zeek code.

    - We accept that a single access might be relatively slow due to
      name lookup and data conversion. This is primarily meant for
      configuration-style data, not for transferring lots of dynamic
      state over.

    - In that spirit, we don't support deep-copying complex data types
      from Zeek over to Spicy. This is (1) to avoid performance
      problems when accidentally copying large containers over,
      potentially even at every access; and (2) to avoid the two sides
      getting out of sync if one ends up modifying a container without
      the other being able to see it.
This commit is contained in:
Robin Sommer 2024-06-17 09:00:37 +02:00
parent 93dd9d6797
commit 4fc57294f1
No known key found for this signature in database
GPG key ID: D8187293B3FFE5D0
5 changed files with 750 additions and 9 deletions

View file

@ -19,7 +19,10 @@
#include <hilti/rt/extension-points.h>
#include <hilti/rt/fmt.h>
#include <hilti/rt/types/all.h>
#include <hilti/rt/util.h>
#include "IntrusivePtr.h"
#include "Type.h"
#include "zeek/Desc.h"
#include "zeek/Val.h"
#include "zeek/spicy/cookie.h"
@ -71,9 +74,9 @@ class TypeMismatch : public UsageError {
*/
class ParameterMismatch : public TypeMismatch {
public:
ParameterMismatch(const std::string_view& msg, std::string_view location = "")
ParameterMismatch(std::string_view msg, std::string_view location = "")
: TypeMismatch(hilti::rt::fmt("Event parameter mismatch, %s", msg)) {}
ParameterMismatch(const std::string_view& have, const TypePtr& want, std::string_view location = "")
ParameterMismatch(std::string_view have, const TypePtr& want, std::string_view location = "")
: ParameterMismatch(_fmt(have, want)) {}
private:
@ -97,13 +100,13 @@ public:
* Begins registration of a Spicy EVT module. All subsequent, other `register_*()`
* function call will be associated with this module for documentation purposes.
*/
void register_spicy_module_begin(const std::string& name, const std::string& description);
void register_spicy_module_begin(const std::string& id, const std::string& description);
/**
* Registers a Spicy protocol analyzer with its EVT meta information with the
* plugin's runtime.
*/
void register_protocol_analyzer(const std::string& name, hilti::rt::Protocol proto,
void register_protocol_analyzer(const std::string& id, hilti::rt::Protocol proto,
const hilti::rt::Vector<::zeek::spicy::rt::PortRange>& ports,
const std::string& parser_orig, const std::string& parser_resp,
const std::string& replaces, const std::string& linker_scope);
@ -112,7 +115,7 @@ void register_protocol_analyzer(const std::string& name, hilti::rt::Protocol pro
* Registers a Spicy file analyzer with its EVT meta information with the
* plugin's runtime.
*/
void register_file_analyzer(const std::string& name, const hilti::rt::Vector<std::string>& mime_types,
void register_file_analyzer(const std::string& id, const hilti::rt::Vector<std::string>& mime_types,
const std::string& parser, const std::string& replaces, const std::string& linker_scope);
/** Reports a Zeek-side "weird". */
@ -122,7 +125,7 @@ void weird(const std::string& id, const std::string& addl);
* Registers a Spicy packet analyzer with its EVT meta information with the
* plugin's runtime.
*/
void register_packet_analyzer(const std::string& name, const std::string& parser, const std::string& replaces,
void register_packet_analyzer(const std::string& id, const std::string& parser, const std::string& replaces,
const std::string& linker_scope);
/** Registers a Spicy-generated type to make it available inside Zeek. */
@ -991,4 +994,328 @@ inline ValPtr to_val(const T& t, const TypePtr& target) {
return target->AsEnumType()->GetEnumVal(bt);
}
/**
* Returns the Zeek value associated with a global Zeek-side ID. Throws if the
* ID does not exist.
*/
inline ValPtr get_value(const std::string& name) {
if ( auto id = zeek::detail::global_scope()->Find(name) )
return id->GetVal();
else
throw InvalidValue(util::fmt("no such Zeek variable: '%s'", name.c_str()));
}
namespace detail {
/** Helper to raise a ``TypeMismatch`` exception. */
inline auto type_mismatch(const ValPtr& v, const char* expected) {
throw TypeMismatch(util::fmt("type mismatch in Zeek value: expected %s, but got %s", expected,
::zeek::type_name(v->GetType()->Tag())));
}
/**
* Helper to check the type of Zeek value against an expected type tag, raising
* a ``TypeMismatch`` exception on mismatch.
*/
inline auto check_type(const ValPtr& v, ::zeek::TypeTag type_tag, const char* expected) {
if ( v->GetType()->Tag() != type_tag )
type_mismatch(v, expected);
}
} // namespace detail
/** Type for a Zeek record value. */
using ValRecordPtr = ::zeek::IntrusivePtr<::zeek::RecordVal>;
/** Type for a Zeek set value. */
using ValSetPtr = ::zeek::IntrusivePtr<::zeek::TableVal>;
/** Type for a Zeek table value. */
using ValTablePtr = ::zeek::IntrusivePtr<::zeek::TableVal>;
/** Type for a Zeek vector value. */
using ValVectorPtr = ::zeek::IntrusivePtr<::zeek::VectorVal>;
/** Converts a Zeek `addr` value to its Spicy equivalent. Throws on error. */
inline ::hilti::rt::Address as_address(const ValPtr& v) {
detail::check_type(v, TYPE_ADDR, "address");
return ::hilti::rt::Address(v->AsAddr());
}
/** Converts a Zeek `bool` value to its Spicy equivalent. Throws on error. */
inline ::hilti::rt::Bool as_bool(const ValPtr& v) {
detail::check_type(v, TYPE_BOOL, "bool");
return ::hilti::rt::Bool(v->AsBool());
}
/** Converts a Zeek `count` value to its Spicy equivalent. Throws on error. */
inline hilti::rt::integer::safe<uint64_t> as_count(const ValPtr& v) {
detail::check_type(v, TYPE_COUNT, "count");
return v->AsCount();
}
/** Converts a Zeek `double` value to its Spicy equivalent. Throws on error. */
inline double as_double(const ValPtr& v) {
detail::check_type(v, TYPE_DOUBLE, "double");
return v->AsDouble();
}
/**
* Converts a Zeek `enum` value to a string containing the (unscoped) label
* name. Throws on error.
*/
inline std::string as_enum(const ValPtr& v) {
detail::check_type(v, TYPE_ENUM, "enum");
// Zeek returns the name as "<module>::<enum>", we just want the enum name.
return hilti::rt::rsplit1(v->GetType()->AsEnumType()->Lookup(v->AsEnum()), "::").second;
}
/** Converts a Zeek `int` value to its Spicy equivalent. Throws on error. */
inline hilti::rt::integer::safe<int64_t> as_int(const ValPtr& v) {
detail::check_type(v, TYPE_INT, "int");
return v->AsInt();
}
/** Converts a Zeek `interval` value to its Spicy equivalent. Throws on error. */
inline ::hilti::rt::Interval as_interval(const ValPtr& v) {
detail::check_type(v, TYPE_INTERVAL, "interval");
return ::hilti::rt::Interval(v->AsInterval(), hilti::rt::Interval::SecondTag{});
}
/** Converts a Zeek `port` value to its Spicy equivalent. Throws on error. */
inline ::hilti::rt::Port as_port(const ValPtr& v) {
detail::check_type(v, TYPE_PORT, "port");
auto p = v->AsPortVal();
// Wrap port number into safe integer to catch any overflows (Zeek returns
// an uint32, while HILTI wants an uint16).
return ::hilti::rt::Port(hilti::rt::integer::safe<uint16_t>(p->Port()), p->PortType());
}
/** Converts a Zeek `record` value to its Spicy equivalent. Throws on error. */
inline ValRecordPtr as_record(const ValPtr& v) {
detail::check_type(v, TYPE_RECORD, "record");
return ::zeek::cast_intrusive<::zeek::RecordVal>(v);
}
/** Converts a Zeek `set` value to its Spicy equivalent. Throws on error. */
inline ValSetPtr as_set(const ValPtr& v) {
detail::check_type(v, TYPE_TABLE, "set");
if ( ! v->AsTableVal()->GetType()->IsSet() )
detail::type_mismatch(v, "set");
return ::zeek::cast_intrusive<::zeek::TableVal>(v);
}
/** Converts a Zeek `string` value to its Spicy equivalent. Throws on error. */
inline hilti::rt::Bytes as_string(const ValPtr& v) {
detail::check_type(v, TYPE_STRING, "string");
auto str = v->AsString();
return hilti::rt::Bytes(reinterpret_cast<const char*>(str->Bytes()), str->Len());
}
/** Converts a Zeek `subnet` value to its Spicy equivalent. Throws on error. */
inline ::hilti::rt::Network as_subnet(const ValPtr& v) {
detail::check_type(v, TYPE_SUBNET, "subnet");
auto subnet = v->AsSubNet();
return ::hilti::rt::Network(subnet.Prefix(), subnet.Length());
}
/** Converts a Zeek `table` value to its Spicy equivalent. Throws on error. */
inline ValTablePtr as_table(const ValPtr& v) {
detail::check_type(v, TYPE_TABLE, "table");
if ( v->AsTableVal()->GetType()->IsSet() )
detail::type_mismatch(v, "table");
return ::zeek::cast_intrusive<::zeek::TableVal>(v);
}
/** Converts a Zeek `time` value to its Spicy equivalent. Throws on error. */
inline ::hilti::rt::Time as_time(const ValPtr& v) {
detail::check_type(v, TYPE_TIME, "time");
return ::hilti::rt::Time(v->AsTime(), hilti::rt::Time::SecondTag{});
}
/** Converts a Zeek `vector` value to its Spicy equivalent. Throws on error. */
inline ValVectorPtr as_vector(const ValPtr& v) {
detail::check_type(v, TYPE_VECTOR, "vector");
return ::zeek::cast_intrusive<::zeek::VectorVal>(v);
}
/** Retrieves a global Zeek variable of assumed type `addr`. Throws on error. */
inline hilti::rt::Address get_address(const std::string& name) { return as_address(get_value(name)); }
/** Retrieves a global Zeek variable of assumed type `bool`. Throws on error. */
inline hilti::rt::Bool get_bool(const std::string& name) { return as_bool(get_value(name)); }
/** Retrieves a global Zeek variable of assumed type `count`. Throws on error. */
inline hilti::rt::integer::safe<uint64_t> get_count(const std::string& name) { return as_count(get_value(name)); }
/** Retrieves a global Zeek variable of assumed type `double`. Throws on error. */
inline double get_double(const std::string& name) { return as_double(get_value(name)); }
/**
* Retrieves a global Zeek variable of assumed type `enum` as a string
* containing the (unscoped) label name. Throws on error.
*/
inline std::string get_enum(const std::string& name) { return as_enum(get_value(name)); }
/** Retrieves a global Zeek variable of assumed type `int`. Throws on error. */
inline hilti::rt::integer::safe<int64_t> get_int(const std::string& name) { return as_int(get_value(name)); }
/** Retrieves a global Zeek variable of assumed type `interval`. Throws on error. */
inline hilti::rt::Interval get_interval(const std::string& name) { return as_interval(get_value(name)); }
/** Retrieves a global Zeek variable of assumed type `port`. Throws on error. */
inline hilti::rt::Port get_port(const std::string& name) { return as_port(get_value(name)); }
/** Retrieves a global Zeek variable of assumed type `record`. Throws on error. */
inline ValRecordPtr get_record(const std::string& name) { return as_record(get_value(name)); }
/** Retrieves a global Zeek variable of assumed type `set`. Throws on error. */
inline ValSetPtr get_set(const std::string& name) { return as_set(get_value(name)); }
/** Retrieves a global Zeek variable of assumed type `string`. Throws on error. */
inline hilti::rt::Bytes get_string(const std::string& name) { return as_string(get_value(name)); }
/** Retrieves a global Zeek variable of assumed type `subnet`. Throws on error. */
inline hilti::rt::Network get_subnet(const std::string& name) { return as_subnet(get_value(name)); }
/** Retrieves a global Zeek variable of assumed type `table`. Throws on error. */
inline ValTablePtr get_table(const std::string& name) { return as_table(get_value(name)); }
/** Retrieves a global Zeek variable of assumed type `time`. Throws on error. */
inline hilti::rt::Time get_time(const std::string& name) { return as_time(get_value(name)); }
/** Retrieves a global Zeek variable of assumed type `vector`. Throws on error. */
inline ValVectorPtr get_vector(const std::string& name) { return as_vector(get_value(name)); }
/** Retrieves the value of Zeek record field. Throws on error. */
inline ::zeek::ValPtr record_field(const zeek::spicy::rt::ValRecordPtr& v, const std::string& field) {
auto index = v->GetType()->AsRecordType()->FieldOffset(field.c_str());
if ( index < 0 )
throw InvalidValue(util::fmt("no such record field: %s", field.c_str()));
if ( auto x = v->GetFieldOrDefault(index) )
return x;
else
throw InvalidValue(util::fmt("record field is not set: %s", field.c_str()));
}
/** Retrieves the value of Zeek record field. Throws on error. */
inline ::zeek::ValPtr record_field(const std::string& name, const std::string& index) {
return record_field(get_record(name), index);
}
/** Check if a Zeek record has a field's value set. Throws on errors. */
inline hilti::rt::Bool record_has_value(const zeek::spicy::rt::ValRecordPtr& v, const std::string& field) {
auto index = v->GetType()->AsRecordType()->FieldOffset(field.c_str());
if ( index < 0 )
throw InvalidValue(util::fmt("no such field in record type: %s", field.c_str()));
return v->HasField(index);
}
/** Checks if a Zeek record has a field's value set. Throws on errors. */
inline hilti::rt::Bool record_has_value(const std::string& name, const std::string& index) {
return record_has_value(get_record(name), index);
}
/** Check if a Zeek record type has a field of a give name. Throws on errors. */
inline hilti::rt::Bool record_has_field(const zeek::spicy::rt::ValRecordPtr& v, const std::string& field) {
return v->GetType()->AsRecordType()->FieldOffset(field.c_str()) >= 0;
}
/** Check if a Zeek record type has a field of a give name. Throws on errors. */
inline hilti::rt::Bool record_has_field(const std::string& name, const std::string& index) {
return record_has_value(get_record(name), index);
}
/** Checks if a Zeek set contains a given element. Throws on errors. */
template<typename T>
::hilti::rt::Bool set_contains(const ValSetPtr& v, const T& key) {
auto index = v->GetType()->AsTableType()->GetIndexTypes()[0];
return (v->Find(to_val(key, index)) != nullptr);
}
/** Checks if a Zeek set contains a given element. Throws on errors. */
template<typename T>
::hilti::rt::Bool set_contains(const std::string& name, const T& key) {
return set_contains(get_set(name), key);
}
/** Checks if a Zeek table contains a given element. Throws on errors. */
template<typename T>
::hilti::rt::Bool table_contains(const ValTablePtr& v, const T& key) {
auto index = v->GetType()->AsTableType()->GetIndexTypes()[0];
return (v->Find(to_val(key, index)) != nullptr);
}
/** Check if a Zeek table contains a given element. Throws on errors. */
template<typename T>
::hilti::rt::Bool table_contains(const std::string& name, const T& key) {
return table_contains(get_table(name), key);
}
/**
* Retrieves a value from a Zeek table. Returns an error value if the key does
* not exist. Throws on other errors.
*/
template<typename T>
std::optional<::zeek::ValPtr> table_lookup(const zeek::spicy::rt::ValTablePtr& v, const T& key) {
auto index = v->GetType()->AsTableType()->GetIndexTypes()[0];
if ( auto x = v->FindOrDefault(to_val(key, index)) )
return x;
else
return {};
}
/**
* Retrieves a value from a Zeek table. Returns an error value if the key does
* not exist. Throws on other errors.
*/
template<typename T>
std::optional<::zeek::ValPtr> table_lookup(const std::string& name, const T& key) {
return table_lookup(get_table(name), key);
}
/** Returns a Zeek vector element. Throws on errors. */
inline ::zeek::ValPtr vector_index(const zeek::spicy::rt::ValVectorPtr& v,
const hilti::rt::integer::safe<uint64_t>& index) {
if ( index >= v->Size() )
throw InvalidValue(util::fmt("vector index out of bounds: %" PRIu64, index.Ref()));
return v->ValAt(index);
}
/** Returns a Zeek vector element. Throws on errors. */
inline ::zeek::ValPtr vector_index(const std::string& name, const hilti::rt::integer::safe<uint64_t>& index) {
return vector_index(get_vector(name), index);
}
/** Returns the size of a Zeek vector. Throws on errors. */
inline hilti::rt::integer::safe<uint64_t> vector_size(const zeek::spicy::rt::ValVectorPtr& v) { return v->Size(); }
/** Returns the size of a Zeek vector. Throws on errors. */
inline hilti::rt::integer::safe<uint64_t> vector_size(const std::string& name) { return vector_size(get_vector(name)); }
} // namespace zeek::spicy::rt
namespace hilti::rt::detail::adl {
// Stringification for opaque type handles.
inline std::string to_string(const zeek::ValPtr& v, detail::adl::tag /* unused */) { return "<Zeek value>"; }
inline std::string to_string(const zeek::spicy::rt::ValRecordPtr& v, detail::adl::tag /* unused */) {
return "<Zeek record>";
}
inline std::string to_string(const zeek::spicy::rt::ValTablePtr& v, detail::adl::tag /* unused */) {
return "<Zeek set/table>";
}
inline std::string to_string(const zeek::spicy::rt::ValVectorPtr& v, detail::adl::tag /* unused */) {
return "<Zeek vector>";
}
} // namespace hilti::rt::detail::adl