// See the file "COPYING" in the main distribution directory for copyright. /** * Functions and types available to generated Spicy/Zeek glue code. */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include "zeek/Desc.h" #include "zeek/IntrusivePtr.h" #include "zeek/Type.h" #include "zeek/Val.h" #include "zeek/spicy/cookie.h" #include "zeek/spicy/manager.h" #include "zeek/spicy/port-range.h" namespace zeek::spicy::rt { // Adapt to rename of exception. using UsageError = ::hilti::rt::UsageError; /** * Exception thrown by event generation code if the value of an `$...` * expression isn't available. */ class ValueUnavailable : public UsageError { public: using UsageError::UsageError; }; /** * Exception thrown by event generation code if the values can't be converted * to Zeek. */ class InvalidValue : public UsageError { public: using UsageError::UsageError; }; /** * Exception thrown by event generation code if functionality is used * that the current build does not support. */ class Unsupported : public UsageError { public: using UsageError::UsageError; }; /** * Exception thrown if there's a type mismatch between Spicy and Zeek side. */ class TypeMismatch : public UsageError { using UsageError::UsageError; }; /** * Exception thrown by event generation code if there's a type mismatch between * a Spicy-side parameter value and what the Zeek event expects. */ class ParameterMismatch : public TypeMismatch { public: ParameterMismatch(std::string_view msg, std::string_view location = "") : TypeMismatch(hilti::rt::fmt("Event parameter mismatch, %s", msg)) {} ParameterMismatch(std::string_view have, const TypePtr& want, std::string_view location = "") : ParameterMismatch(_fmt(have, want)) {} private: static std::string _fmt(const std::string_view& have, const TypePtr& want) { ODesc d; want->Describe(&d); return hilti::rt::fmt("cannot convert Spicy value of type '%s' to Zeek value of type '%s'", have, d.Description()); } }; /** * Exception thrown by the runtime library when Zeek has flagged a problem. */ class ZeekError : public UsageError { public: using UsageError::UsageError; }; /** * 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& 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& 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); /** * Registers a Spicy file analyzer with its EVT meta information with the * plugin's runtime. */ void register_file_analyzer(const std::string& id, const hilti::rt::Vector& mime_types, const std::string& parser, const std::string& replaces, const std::string& linker_scope); /** Reports a Zeek-side "weird". */ 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& 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. */ void register_type(const std::string& ns, const std::string& id, const TypePtr& type); /** * Ends registration of a Spicy EVT module. This must follow a preceding * `registerSpicyModuleBegin()`. */ void register_spicy_module_end(); /** Identifies a Zeek-side type. */ enum class ZeekTypeTag : uint64_t { Addr, Any, Bool, Count, Double, Enum, Error, File, Func, Int, Interval, List, Opaque, Pattern, Port, Record, String, Subnet, Table, Time, Type, Vector, Void, }; extern TypePtr create_base_type(ZeekTypeTag tag); extern TypePtr create_enum_type( const std::string& ns, const std::string& id, const hilti::rt::Vector>>& labels); struct RecordField { std::string id; /**< name of record field */ TypePtr type; /**< Spicy-side type object */ bool is_optional; /**< true if field is optional */ bool is_log; /**< true if field has `&log` */ }; extern TypePtr create_record_type(const std::string& ns, const std::string& id, const hilti::rt::Vector& fields); extern RecordField create_record_field(const std::string& id, const TypePtr& type, hilti::rt::Bool is_optional, hilti::rt::Bool is_log); extern TypePtr create_table_type(TypePtr key, std::optional value); extern TypePtr create_vector_type(const TypePtr& elem); /** Returns true if an event has at least one handler defined. */ inline hilti::rt::Bool have_handler(const EventHandlerPtr& handler) { return static_cast(handler); } /** * Creates a new event handler under the given name. */ void install_handler(const std::string& name); /** * Looks up an event handler by name. The handler must have been installed * before through `install_handler()`. */ EventHandlerPtr internal_handler(const std::string& name); /** Raises a Zeek event, given the handler and arguments. */ void raise_event(const EventHandlerPtr& handler, const hilti::rt::Vector& args); /** * Returns the Zeek type of an event's i'th argument. The result's ref count * is not increased. */ TypePtr event_arg_type(const EventHandlerPtr& handler, const hilti::rt::integer::safe& idx); /** * Retrieves the analyzer ID for the currently processed Zeek connection. * Assumes that the HILTI context's cookie value has been set accordingly. * * @return Pointer to an analyzer instance */ zeek::analyzer::ID current_analyzer_id(); /** * Retrieves the connection ID for the currently processed Zeek connection. * Assumes that the HILTI context's cookie value has been set accordingly. * * @return Zeek value of record type */ ValPtr& current_conn(); /** * Retrieves the direction of the currently processed Zeek connection. * Assumes that the HILTI context's cookie value has been set accordingly. * * @return Zeek value of boolean type */ ValPtr& current_is_orig(); /** * Logs a string through the Spicy plugin's debug output. * * @param cookie refers to the connection or file that the message is associated with * @param msg message to log */ void debug(const Cookie& cookie, const std::string& msg); /** * Logs a string through the Spicy plugin's debug output. This version logs * the information the currently processed connection or file. * * @param msg message to log */ void debug(const std::string& msg); /** * Retrieves the fa_file instance for the currently processed Zeek file. * Assumes that the HILTI context's cookie value has been set accordingly. * * @return Zeek value of record type */ ValPtr current_file(); /** * Retrieves a `raw_pkt_hdr` instance for the currently processed Zeek packet. * Assumes that the HILTI context's cookie value has been set accordingly. * * @return Zeek value of record type */ ValPtr current_packet(); /** * Returns true if we're currently parsing the originator side of a * connection. */ hilti::rt::Bool is_orig(); /** * Returns the current connection's UID. */ std::string uid(); /** * Returns the current connection's ID tuple. */ std::tuple conn_id(); /** Instructs to Zeek to flip the directionality of the current connecction. */ void flip_roles(); /** * Returns the number of packets seen so far on the current side of the * current connection. */ hilti::rt::integer::safe number_packets(); /** * Triggers a DPD protocol confirmation for the currently processed * connection. Assumes that the HILTI context's cookie value has been set * accordingly. */ void confirm_protocol(); /** * Triggers a DPD protocol violation for the currently processed connection. * Assumes that the HILTI context's cookie value has been set accordingly. * * @param reason short description of what went wrong */ void reject_protocol(const std::string& reason = "protocol rejected"); /** * Opaque handle to a protocol analyzer. */ class ProtocolHandle { public: ProtocolHandle() {} explicit ProtocolHandle(uint64_t id, ::hilti::rt::Protocol proto) : _id(id), _proto(proto) {} uint64_t id() const { if ( ! _id ) throw ValueUnavailable("uninitialized protocol handle"); return *_id; } const auto& protocol() const { return _proto; } friend std::string to_string(const ProtocolHandle& h, ::hilti::rt::detail::adl::tag) { if ( ! h._id ) return "(uninitialized protocol handle)"; return std::to_string(*h._id); } friend std::ostream& operator<<(std::ostream& stream, const ProtocolHandle& h) { return stream << ::hilti::rt::to_string(h); } private: std::optional _id; ::hilti::rt::Protocol _proto = ::hilti::rt::Protocol::Undef; }; /** * Adds a Zeek-side child protocol analyzer to the current connection. * * @param analyzer the Zeek-side name of the analyzer to instantiate; can be left unset to add a DPD analyzer */ void protocol_begin(const std::optional& analyzer, const ::hilti::rt::Protocol& proto); /** * Adds a Zeek-side DPD child analyzer to the current connection. * * @param proto the transport-layer protocol of the desired DPD analyzer; must be TCP or UDP */ void protocol_begin(const ::hilti::rt::Protocol& proto); /** * Gets a handle to a child analyzer of a given type. If a child of that type * does not yet exist it will be created. * * @param analyzer the Zeek-side name of the analyzer to get (e.g., `HTTP`) * @param proto the transport-layer protocol of the analyzer, which must match * the type of the child analyzer that *analyzer* refers to * * @return a handle to the child analyzer. When done, the handle should be * closed, either explicitly with protocol_handle_close or implicitly with * protocol_end. */ rt::ProtocolHandle protocol_handle_get_or_create(const std::string& analyzer, const ::hilti::rt::Protocol& proto); /** * Forwards data to all previously instantiated Zeek-side child protocol * analyzers of a given transport-layer protocol. * * @param is_orig true to feed data to originator side, false for responder * @param data next chunk of stream data for child analyzer to process * @param h optional handle to pass data to a specific child analyzer only */ void protocol_data_in(const hilti::rt::Bool& is_orig, const hilti::rt::Bytes& data, const ::hilti::rt::Protocol& proto); /** * Forwards data to a specific previously instantiated Zeek-side child protocol * analyzer. * * @param is_orig true to feed data to originator side, false for responder * @param data next chunk of stream data for child analyzer to process * @param h handle identifying the specific child analyzer only */ void protocol_data_in(const hilti::rt::Bool& is_orig, const hilti::rt::Bytes& data, const ProtocolHandle& h); /** * Signals a gap in input data to all previously instantiated Zeek-side child * protocol analyzers. * * @param is_orig true to signal gap to originator side, false for responder * @param offset of the gap inside the protocol stream * @param length of the gap * @param h optional handle to the child analyzer to signal a gap to */ void protocol_gap(const hilti::rt::Bool& is_orig, const hilti::rt::integer::safe& offset, const hilti::rt::integer::safe& len, const std::optional& h = {}); /** * Signals EOD to all previously instantiated Zeek-side child protocol * analyzers and removes them. */ void protocol_end(); /** * Closes a protocol handle. * * @param handle handle of the protocol analyzer to close. */ void protocol_handle_close(const ProtocolHandle& handle); /** * Signals the beginning of a file to Zeek's file analysis, associating it * with the current connection. * * @param mime_type optional mime type passed to Zeek * @param fid optional file ID passed to Zeek * @returns Zeek-side file ID of the new file */ std::string file_begin(const std::optional& mime_type, const std::optional& fid); /** * Returns the current file's FUID. */ std::string fuid(); /** * Terminates the currently active Zeek-side session, flushing all state. Any * subsequent activity will start a new session from scratch. */ void terminate_session(); /** * Tells Zeek to skip sending any further input data to the current protocol * or file analyzer. */ void skip_input(); /** * Signals the expected size of a file to Zeek's file analysis. * * @param size expected final size of the file * @param fid ID of the file to operate on; if unset, the most recently begun file is used */ void file_set_size(const hilti::rt::integer::safe& size, const std::optional& fid = {}); /** * Passes file content on to Zeek's file analysis. * * @param data next chunk of data * @param fid ID of the file to operate on; if unset, the most recently begun file is used */ void file_data_in(const hilti::rt::Bytes& data, const std::optional& fid = {}); /** * Passes file content at a specific offset on to Zeek's file analysis. * * @param data next chunk of data * @param offset file offset of the data geing passed in * @param fid ID of the file to operate on; if unset, the most recently begun file is used */ void file_data_in_at_offset(const hilti::rt::Bytes& data, const hilti::rt::integer::safe& offset, const std::optional& fid = {}); /** * Signals a gap in a file to Zeek's file analysis. * * @param offset of the gap * @param length of the gap * @param fid ID of the file to operate on; if unset, the most recently begun file is used */ void file_gap(const hilti::rt::integer::safe& offset, const hilti::rt::integer::safe& len, const std::optional& fid = {}); /** * Signals the end of a file to Zeek's file analysis. * * @param fid ID of the file to operate on; if unset, the most recently begun file is used */ void file_end(const std::optional& fid = {}); /** Specifies the next-layer packet analyzer. */ void forward_packet(const hilti::rt::integer::safe& identifier); /** Gets the network time from Zeek. */ hilti::rt::Time network_time(); // Forward-declare to_val() functions. template::value>* = nullptr> ValPtr to_val(const T& t, const TypePtr& target); template inline ValPtr to_val(const hilti::rt::Bitfield& v, const TypePtr& target); template::value>* = nullptr> ValPtr to_val(const T& t, const TypePtr& target); template::value>* = nullptr> ValPtr to_val(const T& t, const TypePtr& target); template::value>* = nullptr> ValPtr to_val(const T& t, const TypePtr& target); template ValPtr to_val(const hilti::rt::Map& s, const TypePtr& target); template ValPtr to_val(const hilti::rt::Set& s, const TypePtr& target); template ValPtr to_val(const hilti::rt::Vector& v, const TypePtr& target); template ValPtr to_val(const std::optional& t, const TypePtr& target); template ValPtr to_val(const hilti::rt::DeferredExpression& t, const TypePtr& target); template ValPtr to_val(hilti::rt::integer::safe i, const TypePtr& target); template ValPtr to_val(const hilti::rt::ValueReference& t, const TypePtr& target); inline ValPtr to_val(const hilti::rt::Bool& b, const TypePtr& target); inline ValPtr to_val(const hilti::rt::Address& d, const TypePtr& target); inline ValPtr to_val(const hilti::rt::Bytes& b, const TypePtr& target); inline ValPtr to_val(const hilti::rt::Interval& t, const TypePtr& target); inline ValPtr to_val(const hilti::rt::Port& d, const TypePtr& target); inline ValPtr to_val(const hilti::rt::Time& t, const TypePtr& target); inline ValPtr to_val(const std::string& s, const TypePtr& target); inline ValPtr to_val(double r, const TypePtr& target); /** * Converts a Spicy-side optional value to a Zeek value. This assumes the * optional is set, and will throw an exception if not. The result is * returned with ref count +1. */ template inline ValPtr to_val(const std::optional& t, const TypePtr& target) { if ( t.has_value() ) return to_val(hilti::rt::optional::value(t), target); return nullptr; } /** * Converts a Spicy-side DeferredExpression value to a Zeek value. Such * result values are returned by the ``.?`` operator. If the result is not * set, this will convert into nullptr (which the tuple-to-record to_val() * picks up on). */ template inline ValPtr to_val(const hilti::rt::DeferredExpression& t, const TypePtr& target) { try { return to_val(t(), target); } catch ( const hilti::rt::AttributeNotSet& ) { return nullptr; } } /** * Converts a Spicy-side string to a Zeek value. The result is returned with * ref count +1. */ inline ValPtr to_val(const std::string& s, const TypePtr& target) { if ( target->Tag() != TYPE_STRING ) throw ParameterMismatch("string", target); return make_intrusive(s); } /** * Converts a Spicy-side bytes instance to a Zeek value. The result is returned with * ref count +1. */ inline ValPtr to_val(const hilti::rt::Bytes& b, const TypePtr& target) { if ( target->Tag() != TYPE_STRING ) throw ParameterMismatch("string", target); return make_intrusive(b.str()); } /** * Converts a Spicy-side integer to a Zeek value. The result is * returned with ref count +1. */ template inline ValPtr to_val(hilti::rt::integer::safe i, const TypePtr& target) { ValPtr v = nullptr; if constexpr ( std::is_unsigned::value ) { if ( target->Tag() == TYPE_COUNT ) return val_mgr->Count(i); if ( target->Tag() == TYPE_INT ) return val_mgr->Int(i); throw ParameterMismatch("uint64", target); } else { if ( target->Tag() == TYPE_INT ) return val_mgr->Int(i); if ( target->Tag() == TYPE_COUNT ) { if ( i >= 0 ) return val_mgr->Count(i); else throw ParameterMismatch("negative int64", target); } throw ParameterMismatch("int64", target); } } template ValPtr to_val(const hilti::rt::ValueReference& t, const TypePtr& target) { if ( auto* x = t.get() ) return to_val(*x, target); return nullptr; } /** * Converts a Spicy-side signed bool to a Zeek value. The result is * returned with ref count +1. */ inline ValPtr to_val(const hilti::rt::Bool& b, const TypePtr& target) { if ( target->Tag() != TYPE_BOOL ) throw ParameterMismatch("bool", target); return val_mgr->Bool(b); } /** * Converts a Spicy-side real to a Zeek value. The result is returned with * ref count +1. */ inline ValPtr to_val(double r, const TypePtr& target) { if ( target->Tag() != TYPE_DOUBLE ) throw ParameterMismatch("double", target); return make_intrusive(r); } /** * Converts a Spicy-side address to a Zeek value. The result is returned with * ref count +1. */ inline ValPtr to_val(const hilti::rt::Address& d, const TypePtr& target) { if ( target->Tag() != TYPE_ADDR ) throw ParameterMismatch("addr", target); auto in_addr = d.asInAddr(); if ( auto v4 = std::get_if(&in_addr) ) return make_intrusive(IPAddr(*v4)); else { auto v6 = std::get(in_addr); return make_intrusive(IPAddr(v6)); } } /** * Converts a Spicy-side address to a Zeek value. The result is returned with * ref count +1. */ inline ValPtr to_val(const hilti::rt::Port& p, const TypePtr& target) { if ( target->Tag() != TYPE_PORT ) throw ParameterMismatch("port", target); switch ( p.protocol().value() ) { case hilti::rt::Protocol::TCP: return val_mgr->Port(p.port(), ::TransportProto::TRANSPORT_TCP); case hilti::rt::Protocol::UDP: return val_mgr->Port(p.port(), ::TransportProto::TRANSPORT_UDP); case hilti::rt::Protocol::ICMP: return val_mgr->Port(p.port(), ::TransportProto::TRANSPORT_ICMP); default: throw InvalidValue("port value with undefined protocol"); } } /** * Converts a Spicy-side time to a Zeek value. The result is returned with * ref count +1. */ inline ValPtr to_val(const hilti::rt::Interval& i, const TypePtr& target) { if ( target->Tag() != TYPE_INTERVAL ) throw ParameterMismatch("interval", target); return make_intrusive(i.seconds()); } /** * Converts a Spicy-side time to a Zeek value. The result is returned with * ref count +1. */ inline ValPtr to_val(const hilti::rt::Time& t, const TypePtr& target) { if ( target->Tag() != TYPE_TIME ) throw ParameterMismatch("time", target); return make_intrusive(t.seconds()); } /** * Converts a Spicy-side vector to a Zeek value. The result is returned with * ref count +1. */ template inline ValPtr to_val(const hilti::rt::Vector& v, const TypePtr& target) { if ( target->Tag() != TYPE_VECTOR && target->Tag() != TYPE_LIST ) throw ParameterMismatch("expected vector or list", target); auto vt = cast_intrusive(target); auto zv = make_intrusive(vt); for ( const auto& i : v ) zv->Assign(zv->Size(), to_val(i, vt->Yield())); return std::move(zv); } /** * Converts a Spicy-side map to a Zeek value. The result is returned with * ref count +1. */ template inline ValPtr to_val(const hilti::rt::Map& m, const TypePtr& target) { if constexpr ( hilti::rt::is_tuple::value ) throw ParameterMismatch("internal error: sets with tuples not yet supported in to_val()"); if ( target->Tag() != TYPE_TABLE ) throw ParameterMismatch("map", target); auto tt = cast_intrusive(target); if ( tt->IsSet() ) throw ParameterMismatch("map", target); if ( tt->GetIndexTypes().size() != 1 ) throw ParameterMismatch("map with non-tuple elements", target); auto zv = make_intrusive(tt); for ( const auto& i : m ) { auto k = to_val(i.first, tt->GetIndexTypes()[0]); auto v = to_val(i.second, tt->Yield()); zv->Assign(std::move(k), std::move(v)); } return zv; } // namespace spicy::rt /** * Converts a Spicy-side set to a Zeek value. The result is returned with * ref count +1. */ template inline ValPtr to_val(const hilti::rt::Set& s, const TypePtr& target) { if ( target->Tag() != TYPE_TABLE ) throw ParameterMismatch("set", target); auto tt = cast_intrusive(target); if ( ! tt->IsSet() ) throw ParameterMismatch("set", target); auto zv = make_intrusive(tt); for ( const auto& i : s ) { if constexpr ( hilti::rt::is_tuple::value ) throw ParameterMismatch("internal error: sets with tuples not yet supported in to_val()"); else { if ( tt->GetIndexTypes().size() != 1 ) throw ParameterMismatch("set with non-tuple elements", target); auto idx = to_val(i, tt->GetIndexTypes()[0]); zv->Assign(std::move(idx), nullptr); } } return zv; } namespace { template typename> struct is_instance_impl : std::false_type {}; template typename U, typename... Ts> struct is_instance_impl, U> : std::true_type {}; } // namespace template typename U> using is_instance = is_instance_impl, U>; template inline void set_record_field(RecordVal* rval, const IntrusivePtr& rtype, int idx, const T& x) { using NoConversionNeeded = std::integral_constant< bool, std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v>; using IsSignedInteger = std::integral_constant> || std::is_same_v> || std::is_same_v> || std::is_same_v>>; using IsUnsignedInteger = std::integral_constant> || std::is_same_v> || std::is_same_v> || std::is_same_v>>; if constexpr ( NoConversionNeeded::value ) rval->Assign(idx, x); else if constexpr ( IsSignedInteger::value ) rval->Assign(idx, static_cast(x.Ref())); else if constexpr ( IsUnsignedInteger::value ) rval->Assign(idx, static_cast(x.Ref())); else if constexpr ( std::is_same_v ) rval->Assign(idx, x.str()); else if constexpr ( std::is_same_v ) rval->Assign(idx, static_cast(x)); else if constexpr ( std::is_same_v ) rval->Assign(idx, x); else if constexpr ( std::is_same_v ) rval->AssignTime(idx, x.seconds()); else if constexpr ( std::is_same_v ) rval->AssignInterval(idx, x.seconds()); else if constexpr ( std::is_same_v ) { // "Null" turns into an unset optional record field. } else if constexpr ( is_instance::value ) { if ( x.has_value() ) set_record_field(rval, rtype, idx, *x); } else if constexpr ( is_instance::value ) { try { set_record_field(rval, rtype, idx, x()); } catch ( const hilti::rt::AttributeNotSet& ) { // leave unset } } else { ValPtr v = nullptr; // This may return a nullptr in cases where the field is to be left unset. v = to_val(x, rtype->GetFieldType(idx)); if ( v ) rval->Assign(idx, v); else { // Field must be &optional or &default. if ( auto attrs = rtype->FieldDecl(idx)->attrs; ! attrs || ! (attrs->Find(detail::ATTR_DEFAULT) || attrs->Find(detail::ATTR_OPTIONAL)) ) throw ParameterMismatch(hilti::rt::fmt("missing initialization for field '%s'", rtype->FieldName(idx))); } } } /** * Converts a Spicy-side tuple to a Zeek record value. The result is returned * with ref count +1. */ template::value>*> inline ValPtr to_val(const T& t, const TypePtr& target) { if ( target->Tag() != TYPE_RECORD ) throw ParameterMismatch("tuple", target); auto rtype = cast_intrusive(target); if ( std::tuple_size::value != rtype->NumFields() ) throw ParameterMismatch("tuple", target); auto rval = make_intrusive(rtype); size_t idx = 0; hilti::rt::tuple_for_each(t, [&](const auto& x) { set_record_field(rval.get(), rtype, idx++, x); }); return rval; } /** * Converts a Spicy-side bitfield to a Zeek record value. The result is returned * with ref count +1. */ template inline ValPtr to_val(const hilti::rt::Bitfield& v, const TypePtr& target) { using Bitfield = hilti::rt::Bitfield; if ( target->Tag() != TYPE_RECORD ) throw ParameterMismatch("bitfield", target); auto rtype = cast_intrusive(target); if ( sizeof...(Ts) - 1 != rtype->NumFields() ) throw ParameterMismatch("bitfield", target); auto rval = make_intrusive(rtype); size_t idx = 0; hilti::rt::tuple_for_each(v.value, [&](const auto& x) { if ( idx < sizeof...(Ts) - 1 ) // last element is original integer value, with no record equivalent set_record_field(rval.get(), rtype, idx++, x); }); return rval; } template constexpr bool is_optional_impl = false; template constexpr bool is_optional_impl> = true; template constexpr bool is_optional = is_optional_impl>>; /** * Converts Spicy-side struct to a Zeek record value. The result is returned * with a ref count +1. */ template::value>*> inline ValPtr to_val(const T& t, const TypePtr& target) { if ( target->Tag() != TYPE_RECORD ) throw ParameterMismatch("struct", target); auto rtype = cast_intrusive(target); auto rval = make_intrusive(rtype); int idx = 0; auto num_fields = rtype->NumFields(); t.__visit([&](std::string_view name, const auto& val) { if ( idx >= num_fields ) throw ParameterMismatch(hilti::rt::fmt("no matching record field for field '%s'", name)); // Special-case: Lift up anonymous bitfields (which always come as std::optionals). if ( name == "" ) { using X = typename std::decay::type; if constexpr ( is_optional ) { if constexpr ( std::is_base_of<::hilti::rt::trait::isBitfield, typename X::value_type>::value ) { size_t j = 0; hilti::rt::tuple_for_each(val->value, [&](const auto& x) { if ( j++ < std::tuple_sizevalue)>() - 1 ) // last element is original integer value, with no record equivalent set_record_field(rval.get(), rtype, idx++, x); }); return; } } // There can't be any other anonymous fields. auto msg = hilti::rt::fmt("unexpected anonymous field: %s", name); reporter->InternalError("%s", msg.c_str()); } else { auto field = rtype->GetFieldType(idx); std::string field_name = rtype->FieldName(idx); if ( field_name != name ) throw ParameterMismatch( hilti::rt::fmt("mismatch in field name: expected '%s', found '%s'", name, field_name)); set_record_field(rval.get(), rtype, idx++, val); } }); // We already check above that all Spicy-side fields are mapped so we // can only hit this if there are uninitialized Zeek-side fields left. if ( idx != num_fields ) throw ParameterMismatch(hilti::rt::fmt("missing initialization for field '%s'", rtype->FieldName(idx + 1))); return rval; } /** Maps HILTI's `Protocol` enum to Zeek's `transport_proto` enum. */ inline ValPtr to_val_for_transport_proto(int64_t val, const TypePtr& target) { switch ( val ) { case hilti::rt::Protocol::TCP: return id::transport_proto->GetEnumVal(::TransportProto::TRANSPORT_TCP); case hilti::rt::Protocol::UDP: return id::transport_proto->GetEnumVal(::TransportProto::TRANSPORT_UDP); case hilti::rt::Protocol::ICMP: return id::transport_proto->GetEnumVal(::TransportProto::TRANSPORT_ICMP); case hilti::rt::Protocol::Undef: [[fallthrough]]; // just for readability, make Undef explicit default: return id::transport_proto->GetEnumVal(::TransportProto::TRANSPORT_UNKNOWN); } hilti::rt::cannot_be_reached(); } /** * Converts a Spicy-side enum to a Zeek enum value. The result is returned * with ref count +1. */ template::value>*> inline ValPtr to_val(const T& t, const TypePtr& target) { if ( target->Tag() != TYPE_ENUM ) throw ParameterMismatch("enum", target); // We'll usually be getting an int64_t for T, but allow other signed ints // as well. static_assert(std::is_signed>{}); auto it = static_cast(t.value()); // Special case: map enum values to Zeek's semantics. if ( target->GetName() == "transport_proto" ) { if ( ! std::is_same_v ) throw ParameterMismatch(hilti::rt::demangle(typeid(t).name()), target); return to_val_for_transport_proto(it, target); } // Zeek's enum can't be negative, so we swap in max_int for our Undef (-1). if ( it == std::numeric_limits::max() ) // can't allow this ... throw InvalidValue("enum values with value max_int not supported by Zeek integration"); zeek_int_t bt = (it >= 0 ? it : std::numeric_limits<::zeek_int_t>::max()); 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 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 "::", 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 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(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(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 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 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 ::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 ::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 ::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 ::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 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 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& 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& index) { return vector_index(get_vector(name), index); } /** Returns the size of a Zeek vector. Throws on errors. */ inline hilti::rt::integer::safe 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 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 ""; } inline std::string to_string(const zeek::spicy::rt::ValRecordPtr& v, detail::adl::tag /* unused */) { return ""; } inline std::string to_string(const zeek::spicy::rt::ValTablePtr& v, detail::adl::tag /* unused */) { return ""; } inline std::string to_string(const zeek::spicy::rt::ValVectorPtr& v, detail::adl::tag /* unused */) { return ""; } } // namespace hilti::rt::detail::adl