Spicy: Rework code for converting Spicy values to Zeek values.

The logic was template-based so far, which wasn't great because: (1)
conceptually, it models the Spicy types at the wrong layer (C++ rather
than HILTI types), and (2) stopped working with some recent Spicy
updates (which we have temporarily reverted in the meantime to keep
Zeek working).

The new code is based on HILTI's runtime type information and the
corresponding introspection API, pretty much like `spicy-dump` works
as well. This is the recommended approach for working with HILTI
values, and generally much cleaner.

This is on top of https://github.com/zeek/zeek/pull/4300.
This commit is contained in:
Robin Sommer 2025-03-19 12:09:54 +01:00
parent af46322152
commit 000ed528dc
No known key found for this signature in database
GPG key ID: D8187293B3FFE5D0
3 changed files with 424 additions and 481 deletions

View file

@ -1071,3 +1071,422 @@ hilti::rt::Time rt::network_time() {
auto _ = hilti::rt::profiler::start("zeek/rt/network_time"); auto _ = hilti::rt::profiler::start("zeek/rt/network_time");
return hilti::rt::Time(run_state::network_time, hilti::rt::Time::SecondTag()); return hilti::rt::Time(run_state::network_time, hilti::rt::Time::SecondTag());
} }
static ValPtr convertSignedInteger(int64_t i, std::string_view have_type, const TypePtr& target) {
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 rt::ParameterMismatch(hilti::rt::fmt("negative %s", have_type), target);
}
throw rt::ParameterMismatch(have_type, target);
}
static ValPtr convertUnsignedInteger(uint64_t i, std::string_view have_type, const TypePtr& target) {
if ( target->Tag() == TYPE_COUNT )
return val_mgr->Count(i);
if ( target->Tag() == TYPE_INT ) {
if ( i < static_cast<uint64_t>(std::numeric_limits<int64_t>::max()) )
return val_mgr->Int(static_cast<int64_t>(i));
else
throw rt::ParameterMismatch(hilti::rt::fmt("%s too large", have_type), target);
}
throw rt::ParameterMismatch(have_type, target);
}
inline void setRecordField(RecordVal* rval, const IntrusivePtr<RecordType>& rtype, int idx,
const hilti::rt::type_info::Value& v) {
using namespace hilti::rt;
const auto& type = v.type();
switch ( type.tag ) {
case TypeInfo::Bool: rval->Assign(idx, type.bool_->get(v)); return;
case TypeInfo::Bytes: rval->Assign(idx, type.bytes->get(v).str()); return;
case TypeInfo::Interval: rval->AssignInterval(idx, type.interval->get(v).seconds()); return;
case TypeInfo::Optional:
if ( const auto& x = type.optional->value(v) )
setRecordField(rval, rtype, idx, x);
return;
case TypeInfo::Null: return;
case TypeInfo::Real: rval->Assign(idx, type.real->get(v)); return;
case TypeInfo::SignedInteger_int8: rval->Assign(idx, type.signed_integer_int8->get(v)); return;
case TypeInfo::SignedInteger_int16: rval->Assign(idx, type.signed_integer_int16->get(v)); return;
case TypeInfo::SignedInteger_int32: rval->Assign(idx, type.signed_integer_int32->get(v)); return;
case TypeInfo::SignedInteger_int64: rval->Assign(idx, type.signed_integer_int64->get(v)); return;
case TypeInfo::String: rval->Assign(idx, type.string->get(v)); return;
case TypeInfo::Time: rval->AssignTime(idx, type.time->get(v).seconds()); return;
case TypeInfo::UnsignedInteger_uint8: rval->Assign(idx, type.unsigned_integer_uint8->get(v)); return;
case TypeInfo::UnsignedInteger_uint16: rval->Assign(idx, type.unsigned_integer_uint16->get(v)); return;
case TypeInfo::UnsignedInteger_uint32: rval->Assign(idx, type.unsigned_integer_uint32->get(v)); return;
case TypeInfo::UnsignedInteger_uint64: rval->Assign(idx, type.unsigned_integer_uint64->get(v)); return;
case TypeInfo::StrongReference:
if ( const auto& x = type.strong_reference->value(v) )
setRecordField(rval, rtype, idx, x);
return;
case TypeInfo::ValueReference:
if ( const auto& x = type.value_reference->value(v) )
setRecordField(rval, rtype, idx, x);
return;
case TypeInfo::WeakReference:
if ( const auto& x = type.weak_reference->value(v) )
setRecordField(rval, rtype, idx, x);
return;
case TypeInfo::Address:
case TypeInfo::Bitfield:
case TypeInfo::Enum:
case TypeInfo::Map:
case TypeInfo::Port:
case TypeInfo::Set:
case TypeInfo::Struct:
case TypeInfo::Tuple:
case TypeInfo::Vector: {
// This may return a nullptr in cases where the field is to be left unset.
ValPtr zval = rt::detail::to_val(v, rtype->GetFieldType(idx));
if ( v )
rval->Assign(idx, zval);
else {
// Field must be &optional or &default.
if ( auto attrs = rtype->FieldDecl(idx)->attrs;
! attrs ||
! (attrs->Find(zeek::detail::ATTR_DEFAULT) || attrs->Find(zeek::detail::ATTR_OPTIONAL)) )
throw rt::ParameterMismatch(
hilti::rt::fmt("missing initialization for field '%s'", rtype->FieldName(idx)));
}
return;
}
default: throw zeek::spicy::rt::InvalidValue("unsupported type for record field");
}
hilti::rt::cannot_be_reached();
}
ValPtr rt::detail::to_val(const hilti::rt::type_info::Value& value, const TypePtr& target) {
using namespace hilti::rt;
const auto& type = value.type();
switch ( type.tag ) {
case TypeInfo::Address: {
if ( target->Tag() != TYPE_ADDR )
throw ParameterMismatch(type, target);
auto in_addr = type.address->get(value).asInAddr();
if ( auto v4 = std::get_if<struct in_addr>(&in_addr) )
return make_intrusive<AddrVal>(IPAddr(*v4));
else {
auto v6 = std::get<struct in6_addr>(in_addr);
return make_intrusive<AddrVal>(IPAddr(v6));
}
}
case TypeInfo::Bitfield: {
if ( target->Tag() != TYPE_RECORD )
throw ParameterMismatch(type, target);
auto rtype = cast_intrusive<RecordType>(target);
if ( type.bitfield->bits().size() != static_cast<size_t>(rtype->NumFields()) )
throw ParameterMismatch(type, target);
auto rval = make_intrusive<RecordVal>(rtype);
int idx = 0;
for ( const auto& [bits, bvalue] : type.bitfield->iterate(value) )
setRecordField(rval.get(), rtype, idx++, bvalue);
return std::move(rval);
}
case TypeInfo::Bool: {
if ( target->Tag() != TYPE_BOOL )
throw ParameterMismatch(type, target);
return val_mgr->Bool(type.bool_->get(value));
}
case TypeInfo::Bytes: {
if ( target->Tag() != TYPE_STRING )
throw ParameterMismatch(type, target);
const auto& b = type.bytes->get(value);
return make_intrusive<StringVal>(b.str());
}
case TypeInfo::Enum: {
if ( target->Tag() != TYPE_ENUM )
throw ParameterMismatch(type, target);
auto i = type.enum_->get(value);
if ( target->GetName() == "transport_proto" ) {
// Special case: map Spicy's `Protocol` to Zeek's `transport_proto`.
if ( auto ty = std::string_view(type.display); ty != "hilti::Protocol" && ty != "spicy::Protocol" )
throw ParameterMismatch(type.display, target);
switch ( i.value ) {
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();
}
// Zeek's enum can't be negative, so we swap in max_int for our Undef (-1).
if ( i.value == std::numeric_limits<int64_t>::max() )
// Can't allow this ...
throw InvalidValue("enum values with value max_int not supported by Zeek integration");
zeek_int_t zi = (i.value >= 0 ? i.value : std::numeric_limits<::zeek_int_t>::max());
return target->AsEnumType()->GetEnumVal(zi);
}
case TypeInfo::Interval: {
if ( target->Tag() != TYPE_INTERVAL )
throw ParameterMismatch(type, target);
return make_intrusive<IntervalVal>(type.interval->get(value).seconds());
}
case TypeInfo::Map: {
if ( target->Tag() != TYPE_TABLE )
throw ParameterMismatch(type, target);
if ( type.map->keyType()->tag == TypeInfo::Tuple )
throw ParameterMismatch("internal error: maps with tuples not yet supported in to_val()");
auto tt = cast_intrusive<TableType>(target);
if ( tt->IsSet() )
throw ParameterMismatch(type, target);
if ( tt->GetIndexTypes().size() != 1 )
throw ParameterMismatch(type, target);
auto zv = make_intrusive<TableVal>(tt);
for ( const auto& i : type.map->iterate(value) ) {
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 std::move(zv);
}
case TypeInfo::Optional: {
const auto& x = type.optional->value(value);
return x ? detail::to_val(x, target) : nullptr;
}
case TypeInfo::Port: {
if ( target->Tag() != TYPE_PORT )
throw ParameterMismatch(type, target);
auto p = type.port->get(value);
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");
}
}
case TypeInfo::SignedInteger_int8:
return convertSignedInteger(type.signed_integer_int8->get(value), "int8", target);
case TypeInfo::SignedInteger_int16:
return convertSignedInteger(type.signed_integer_int16->get(value), "int16", target);
case TypeInfo::SignedInteger_int32:
return convertSignedInteger(type.signed_integer_int32->get(value), "int32", target);
case TypeInfo::SignedInteger_int64:
return convertSignedInteger(type.signed_integer_int64->get(value), "int64", target);
case TypeInfo::Time: {
if ( target->Tag() != TYPE_TIME )
throw ParameterMismatch(type, target);
return make_intrusive<TimeVal>(type.time->get(value).seconds());
}
case TypeInfo::Real: {
if ( target->Tag() != TYPE_DOUBLE )
throw ParameterMismatch(type, target);
return make_intrusive<DoubleVal>(type.real->get(value));
}
case TypeInfo::Set: {
if ( target->Tag() != TYPE_TABLE )
throw ParameterMismatch(type, target);
if ( type.set->dereferencedType()->tag == TypeInfo::Tuple )
throw ParameterMismatch("internal error: sets with tuples not yet supported in to_val()");
auto tt = cast_intrusive<TableType>(target);
if ( ! tt->IsSet() )
throw ParameterMismatch(type, target);
auto zv = make_intrusive<TableVal>(tt);
for ( const auto& i : type.set->iterate(value) ) {
if ( tt->GetIndexTypes().size() != 1 )
throw ParameterMismatch(type, target);
auto idx = to_val(i, tt->GetIndexTypes()[0]);
zv->Assign(std::move(idx), nullptr);
}
return std::move(zv);
}
case TypeInfo::String: {
if ( target->Tag() != TYPE_STRING )
throw ParameterMismatch(type, target);
const auto& s = type.string->get(value);
return make_intrusive<StringVal>(s);
}
case TypeInfo::StrongReference: {
const auto& x = type.strong_reference->value(value);
return x ? detail::to_val(x, target) : nullptr;
}
case TypeInfo::Struct: {
if ( target->Tag() != TYPE_RECORD )
throw ParameterMismatch(type, target);
auto rtype = cast_intrusive<RecordType>(target);
auto rval = make_intrusive<RecordVal>(rtype);
auto num_fields = rtype->NumFields();
int idx = 0;
for ( const auto& [field, fvalue] : type.struct_->iterate(value) ) {
if ( idx >= num_fields )
throw ParameterMismatch(hilti::rt::fmt("no matching record field for field '%s'", field.name));
// Special-case: Lift up anonymous bitfields.
if ( field.name == "_anon" ) {
if ( field.type->tag == TypeInfo::Bitfield ) {
size_t j = 0;
for ( const auto& x : field.type->bitfield->iterate(fvalue) )
setRecordField(rval.get(), rtype, idx++, x.second);
continue;
}
// There can't be any other anonymous fields.
auto msg = hilti::rt::fmt("unexpected anonymous field: %s", field.name);
reporter->InternalError("%s", msg.c_str());
}
else {
auto* field_name = rtype->FieldName(idx);
if ( field_name != field.name )
throw ParameterMismatch(hilti::rt::fmt("mismatch in field name: expected '%s', found '%s'",
field.name, field_name));
if ( fvalue )
setRecordField(rval.get(), rtype, idx, fvalue);
idx++;
}
}
// 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)));
return std::move(rval);
}
case TypeInfo::Tuple: {
if ( target->Tag() != TYPE_RECORD )
throw ParameterMismatch(type, target);
auto rtype = cast_intrusive<RecordType>(target);
if ( type.tuple->elements().size() != static_cast<size_t>(rtype->NumFields()) )
throw ParameterMismatch(type, target);
auto rval = make_intrusive<RecordVal>(rtype);
int idx = 0;
for ( const auto& x : type.tuple->iterate(value) ) {
if ( auto fval = x.second.type().optional->value(x.second) ) {
if ( fval )
setRecordField(rval.get(), rtype, idx, fval);
}
idx++;
}
return rval;
}
case TypeInfo::ValueReference: {
const auto& x = type.value_reference->value(value);
return x ? detail::to_val(x, target) : nullptr;
}
case TypeInfo::Vector: {
if ( target->Tag() != TYPE_VECTOR && target->Tag() != TYPE_LIST )
throw ParameterMismatch(type, target);
auto vt = cast_intrusive<VectorType>(target);
auto zv = make_intrusive<VectorVal>(vt);
for ( const auto& i : type.vector->iterate(value) )
zv->Assign(zv->Size(), to_val(i, vt->Yield()));
return std::move(zv);
}
case TypeInfo::UnsignedInteger_uint8:
return convertUnsignedInteger(type.unsigned_integer_uint8->get(value), "uint8", target);
case TypeInfo::UnsignedInteger_uint16:
return convertUnsignedInteger(type.unsigned_integer_uint16->get(value), "uint16", target);
case TypeInfo::UnsignedInteger_uint32:
return convertUnsignedInteger(type.unsigned_integer_uint32->get(value), "uint32", target);
case TypeInfo::UnsignedInteger_uint64:
return convertUnsignedInteger(type.unsigned_integer_uint64->get(value), "uint64", target);
case TypeInfo::WeakReference: {
const auto& x = type.weak_reference->value(value);
return x ? detail::to_val(x, target) : nullptr;
}
default: throw InvalidValue(fmt("unexpected type for conversion to Zeek (%s)", type.display));
}
hilti::rt::cannot_be_reached();
}

View file

@ -491,491 +491,15 @@ void forward_packet(const hilti::rt::integer::safe<uint32_t>& identifier);
/** Gets the network time from Zeek. */ /** Gets the network time from Zeek. */
hilti::rt::Time network_time(); hilti::rt::Time network_time();
// Forward-declare to_val() functions. namespace detail {
template<typename... Ts> extern ValPtr to_val(const hilti::rt::type_info::Value& value, const TypePtr& target);
ValPtr to_val(const hilti::rt::Tuple<Ts...>& t, const hilti::rt::TypeInfo* ti, const TypePtr& target);
template<typename... Ts>
inline ValPtr to_val(const hilti::rt::Bitfield<Ts...>& v, const hilti::rt::TypeInfo* ti, const TypePtr& target);
template<typename T, typename std::enable_if_t<std::is_base_of<::hilti::rt::trait::isStruct, T>::value>* = nullptr>
ValPtr to_val(const T& t, const hilti::rt::TypeInfo* ti, const TypePtr& target);
template<typename T, typename std::enable_if_t<std::is_enum<typename T::Value>::value>* = nullptr>
ValPtr to_val(const T& t, const hilti::rt::TypeInfo* ti, const TypePtr& target);
template<typename T, typename std::enable_if_t<std::is_enum<T>::value>* = nullptr>
ValPtr to_val(const T& t, const hilti::rt::TypeInfo* ti, const TypePtr& target);
template<typename K, typename V>
ValPtr to_val(const hilti::rt::Map<K, V>& s, const hilti::rt::TypeInfo* ti, const TypePtr& target);
template<typename T>
ValPtr to_val(const hilti::rt::Set<T>& s, const hilti::rt::TypeInfo* ti, const TypePtr& target);
template<typename T, typename Allocator>
ValPtr to_val(const hilti::rt::Vector<T, Allocator>& v, const hilti::rt::TypeInfo* ti, const TypePtr& target);
template<typename T>
ValPtr to_val(const std::optional<T>& t, const hilti::rt::TypeInfo* ti, const TypePtr& target);
template<typename T>
ValPtr to_val(hilti::rt::integer::safe<T> i, const hilti::rt::TypeInfo* ti, const TypePtr& target);
template<typename T>
ValPtr to_val(const hilti::rt::ValueReference<T>& t, const hilti::rt::TypeInfo* ti, const TypePtr& target);
inline ValPtr to_val(const hilti::rt::Bool& b, const hilti::rt::TypeInfo* ti, const TypePtr& target);
inline ValPtr to_val(const hilti::rt::Address& d, const hilti::rt::TypeInfo* ti, const TypePtr& target);
inline ValPtr to_val(const hilti::rt::Bytes& b, const hilti::rt::TypeInfo* ti, const TypePtr& target);
inline ValPtr to_val(const hilti::rt::Interval& t, const hilti::rt::TypeInfo* ti, const TypePtr& target);
inline ValPtr to_val(const hilti::rt::Port& d, const hilti::rt::TypeInfo* ti, const TypePtr& target);
inline ValPtr to_val(const hilti::rt::Time& t, const hilti::rt::TypeInfo* ti, const TypePtr& target);
inline ValPtr to_val(const std::string& s, const hilti::rt::TypeInfo* ti, const TypePtr& target);
inline ValPtr to_val(double r, const hilti::rt::TypeInfo* ti, 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<typename T>
inline ValPtr to_val(const std::optional<T>& t, const hilti::rt::TypeInfo* ti, const TypePtr& target) {
if ( t.has_value() )
return to_val(hilti::rt::optional::value(t), nullptr, target);
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 hilti::rt::TypeInfo* ti, const TypePtr& target) {
if ( target->Tag() != TYPE_STRING )
throw ParameterMismatch("string", target);
return make_intrusive<StringVal>(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 hilti::rt::TypeInfo* ti, const TypePtr& target) {
if ( target->Tag() != TYPE_STRING )
throw ParameterMismatch("string", target);
return make_intrusive<StringVal>(b.str());
}
/**
* Converts a Spicy-side integer to a Zeek value. The result is
* returned with ref count +1.
*/
template<typename T>
inline ValPtr to_val(hilti::rt::integer::safe<T> i, const hilti::rt::TypeInfo* ti, const TypePtr& target) {
ValPtr v = nullptr;
if constexpr ( std::is_unsigned<T>::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<typename T> template<typename T>
ValPtr to_val(const hilti::rt::ValueReference<T>& t, const hilti::rt::TypeInfo* ti, const TypePtr& target) { ValPtr to_val(const T& value, const hilti::rt::TypeInfo* type, const TypePtr& target) {
if ( auto* x = t.get() ) return detail::to_val(hilti::rt::type_info::Value(&value, type), target);
return to_val(*x, nullptr, 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 hilti::rt::TypeInfo* ti, 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 hilti::rt::TypeInfo* ti, const TypePtr& target) {
if ( target->Tag() != TYPE_DOUBLE )
throw ParameterMismatch("double", target);
return make_intrusive<DoubleVal>(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 hilti::rt::TypeInfo* ti, const TypePtr& target) {
if ( target->Tag() != TYPE_ADDR )
throw ParameterMismatch("addr", target);
auto in_addr = d.asInAddr();
if ( auto v4 = std::get_if<struct in_addr>(&in_addr) )
return make_intrusive<AddrVal>(IPAddr(*v4));
else {
auto v6 = std::get<struct in6_addr>(in_addr);
return make_intrusive<AddrVal>(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 hilti::rt::TypeInfo* ti, 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 hilti::rt::TypeInfo* ti, const TypePtr& target) {
if ( target->Tag() != TYPE_INTERVAL )
throw ParameterMismatch("interval", target);
return make_intrusive<IntervalVal>(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 hilti::rt::TypeInfo* ti, const TypePtr& target) {
if ( target->Tag() != TYPE_TIME )
throw ParameterMismatch("time", target);
return make_intrusive<TimeVal>(t.seconds());
}
/**
* Converts a Spicy-side vector to a Zeek value. The result is returned with
* ref count +1.
*/
template<typename T, typename Allocator>
inline ValPtr to_val(const hilti::rt::Vector<T, Allocator>& v, const hilti::rt::TypeInfo* ti, const TypePtr& target) {
if ( target->Tag() != TYPE_VECTOR && target->Tag() != TYPE_LIST )
throw ParameterMismatch("expected vector or list", target);
auto vt = cast_intrusive<VectorType>(target);
auto zv = make_intrusive<VectorVal>(vt);
for ( const auto& i : v )
zv->Assign(zv->Size(), to_val(i, nullptr, vt->Yield()));
return std::move(zv);
}
/**
* Converts a Spicy-side map to a Zeek value. The result is returned with
* ref count +1.
*/
template<typename K, typename V>
inline ValPtr to_val(const hilti::rt::Map<K, V>& m, const hilti::rt::TypeInfo* ti, const TypePtr& target) {
if ( target->Tag() != TYPE_TABLE )
throw ParameterMismatch("map", target);
auto tt = cast_intrusive<TableType>(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<TableVal>(tt);
for ( const auto& i : m ) {
auto k = to_val(i.first, nullptr, tt->GetIndexTypes()[0]);
auto v = to_val(i.second, nullptr, 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<typename T>
inline ValPtr to_val(const hilti::rt::Set<T>& s, const hilti::rt::TypeInfo* ti, const TypePtr& target) {
if ( target->Tag() != TYPE_TABLE )
throw ParameterMismatch("set", target);
auto tt = cast_intrusive<TableType>(target);
if ( ! tt->IsSet() )
throw ParameterMismatch("set", target);
auto zv = make_intrusive<TableVal>(tt);
for ( const auto& i : s ) {
if ( tt->GetIndexTypes().size() != 1 )
throw ParameterMismatch("set with non-tuple elements", target);
auto idx = to_val(i, nullptr, tt->GetIndexTypes()[0]);
zv->Assign(std::move(idx), nullptr);
}
return zv;
}
namespace {
template<typename, template<typename...> typename>
struct is_instance_impl : std::false_type {};
template<template<typename...> typename U, typename... Ts>
struct is_instance_impl<U<Ts...>, U> : std::true_type {};
} // namespace
template<typename T, template<typename...> typename U>
using is_instance = is_instance_impl<std::remove_cv_t<T>, U>;
template<typename T>
inline void set_record_field(RecordVal* rval, const IntrusivePtr<RecordType>& rtype, int idx, const T& x) {
using NoConversionNeeded = std::integral_constant<
bool, std::is_same_v<T, int8_t> || std::is_same_v<T, int16_t> || std::is_same_v<T, int32_t> ||
std::is_same_v<T, int64_t> || std::is_same_v<T, uint8_t> || std::is_same_v<T, uint16_t> ||
std::is_same_v<T, uint32_t> || std::is_same_v<T, uint64_t> || std::is_same_v<T, double> ||
std::is_same_v<T, std::string> || std::is_same_v<T, bool>>;
using IsSignedInteger = std::integral_constant<bool, std::is_same_v<T, hilti::rt::integer::safe<int8_t>> ||
std::is_same_v<T, hilti::rt::integer::safe<int16_t>> ||
std::is_same_v<T, hilti::rt::integer::safe<int32_t>> ||
std::is_same_v<T, hilti::rt::integer::safe<int64_t>>>;
using IsUnsignedInteger = std::integral_constant<bool, std::is_same_v<T, hilti::rt::integer::safe<uint8_t>> ||
std::is_same_v<T, hilti::rt::integer::safe<uint16_t>> ||
std::is_same_v<T, hilti::rt::integer::safe<uint32_t>> ||
std::is_same_v<T, hilti::rt::integer::safe<uint64_t>>>;
if constexpr ( NoConversionNeeded::value )
rval->Assign(idx, x);
else if constexpr ( IsSignedInteger::value )
rval->Assign(idx, static_cast<int64_t>(x.Ref()));
else if constexpr ( IsUnsignedInteger::value )
rval->Assign(idx, static_cast<uint64_t>(x.Ref()));
else if constexpr ( std::is_same_v<T, hilti::rt::Bytes> )
rval->Assign(idx, x.str());
else if constexpr ( std::is_same_v<T, hilti::rt::Bool> )
rval->Assign(idx, static_cast<bool>(x));
else if constexpr ( std::is_same_v<T, std::string> )
rval->Assign(idx, x);
else if constexpr ( std::is_same_v<T, hilti::rt::Time> )
rval->AssignTime(idx, x.seconds());
else if constexpr ( std::is_same_v<T, hilti::rt::Interval> )
rval->AssignInterval(idx, x.seconds());
else if constexpr ( std::is_same_v<T, hilti::rt::Null> ) {
// "Null" turns into an unset optional record field.
}
else if constexpr ( is_instance<T, std::optional>::value ) {
if ( x.has_value() )
set_record_field(rval, rtype, idx, *x);
}
else {
ValPtr v = nullptr;
// This may return a nullptr in cases where the field is to be left unset.
v = to_val(x, nullptr, 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<typename... Ts>
ValPtr to_val(const hilti::rt::Tuple<Ts...>& t, const hilti::rt::TypeInfo* ti, const TypePtr& target) {
if ( target->Tag() != TYPE_RECORD )
throw ParameterMismatch("tuple", target);
auto rtype = cast_intrusive<RecordType>(target);
if ( sizeof...(Ts) != rtype->NumFields() )
throw ParameterMismatch("tuple", target);
auto rval = make_intrusive<RecordVal>(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<typename... Ts>
inline ValPtr to_val(const hilti::rt::Bitfield<Ts...>& v, const hilti::rt::TypeInfo* ti, const TypePtr& target) {
using Bitfield = hilti::rt::Bitfield<Ts...>;
if ( target->Tag() != TYPE_RECORD )
throw ParameterMismatch("bitfield", target);
auto rtype = cast_intrusive<RecordType>(target);
if ( sizeof...(Ts) - 1 != rtype->NumFields() )
throw ParameterMismatch("bitfield", target);
auto rval = make_intrusive<RecordVal>(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<typename>
constexpr bool is_optional_impl = false;
template<typename T>
constexpr bool is_optional_impl<std::optional<T>> = true;
template<typename T>
constexpr bool is_optional = is_optional_impl<std::remove_cv_t<std::remove_reference_t<T>>>;
/**
* Converts Spicy-side struct to a Zeek record value. The result is returned
* with a ref count +1.
*/
template<typename T, typename std::enable_if_t<std::is_base_of<::hilti::rt::trait::isStruct, T>::value>*>
inline ValPtr to_val(const T& t, const hilti::rt::TypeInfo* ti, const TypePtr& target) {
if ( target->Tag() != TYPE_RECORD )
throw ParameterMismatch("struct", target);
auto rtype = cast_intrusive<RecordType>(target);
auto rval = make_intrusive<RecordVal>(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 == "<anon>" ) {
using X = typename std::decay<decltype(val)>::type;
if constexpr ( is_optional<X> ) {
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_size<decltype(val->value)>() -
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 hilti::rt::TypeInfo* ti, 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<typename T, typename std::enable_if_t<std::is_enum<typename T::Value>::value>*>
inline ValPtr to_val(const T& t, const hilti::rt::TypeInfo* ti, 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<std::underlying_type_t<typename T::Value>>{});
auto it = static_cast<int64_t>(t.value());
// Special case: map enum values to Zeek's semantics.
if ( target->GetName() == "transport_proto" ) {
if ( ! std::is_same_v<T, hilti::rt::Protocol> )
throw ParameterMismatch(hilti::rt::demangle(typeid(t).name()), target);
return to_val_for_transport_proto(it, nullptr, target);
}
// Zeek's enum can't be negative, so we swap in max_int for our Undef (-1).
if ( it == std::numeric_limits<int64_t>::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 * Returns the Zeek value associated with a global Zeek-side ID. Throws if the
* ID does not exist. * ID does not exist.

View file

@ -1,2 +1,2 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
XXXXXXXXXX.XXXXXX analyzer error in <...>/test.evt, line 5: Event parameter mismatch, cannot convert Spicy value of type 'string' to Zeek value of type 'count' XXXXXXXXXX.XXXXXX analyzer error in <...>/test.evt, line 5: Event parameter mismatch, cannot convert Spicy value of type 'bytes' to Zeek value of type 'count'