mirror of
https://github.com/zeek/zeek.git
synced 2025-10-02 06:38:20 +00:00
Merge remote-tracking branch 'origin/topic/robin/spicy-export-extensions'
* origin/topic/robin/spicy-export-extensions: [Spicy] Clean up representation of EVT record fields. [Spicy] Extend functionality of `export` in EVT files. [Spicy] Refactor parsing of `export` in EVT files.
This commit is contained in:
commit
e8292be0ce
15 changed files with 386 additions and 38 deletions
33
CHANGES
33
CHANGES
|
@ -1,3 +1,36 @@
|
||||||
|
6.1.0-dev.310 | 2023-08-22 14:59:28 +0200
|
||||||
|
|
||||||
|
* GH-3218/GH-3219: Spicy: Extend functionality of `export` in EVT
|
||||||
|
files. (Robin Sommer, Corelight)
|
||||||
|
|
||||||
|
We now support selecting which fields of a unit type get exported into
|
||||||
|
the automatically created Zeek record; as well as selecting which
|
||||||
|
fields get a `&log` attribute added automatically to either all fields
|
||||||
|
or to selected fields.
|
||||||
|
|
||||||
|
Syntax:
|
||||||
|
|
||||||
|
- To export only selected fields:
|
||||||
|
|
||||||
|
export Foo::X with { field1, field3 };
|
||||||
|
|
||||||
|
- To export all but selected fields:
|
||||||
|
|
||||||
|
export Foo::X without { field2, field3 };
|
||||||
|
|
||||||
|
- To `&log` all fields:
|
||||||
|
|
||||||
|
export Foo::X &log;
|
||||||
|
|
||||||
|
- To `&log` only selected fields:
|
||||||
|
|
||||||
|
export Foo::X with { field1 &log, field3 }; # exports (only) field1 and field3, and marks field1 for logging
|
||||||
|
|
||||||
|
* pre-commit: Pin to latest shfmt-py version (Arne Welzel, Corelight)
|
||||||
|
|
||||||
|
This allows users to run shfmt-py with Python > 3.9. Also drop
|
||||||
|
the explicit Python version for the setup-python action.
|
||||||
|
|
||||||
6.1.0-dev.303 | 2023-08-15 17:33:51 +0100
|
6.1.0-dev.303 | 2023-08-15 17:33:51 +0100
|
||||||
|
|
||||||
* Raw reader: use posix_spawn instead of fork + exec (Johanna Amann, Corelight)
|
* Raw reader: use posix_spawn instead of fork + exec (Johanna Amann, Corelight)
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
6.1.0-dev.303
|
6.1.0-dev.310
|
||||||
|
|
|
@ -9,6 +9,7 @@ public type Val = __library_type("::zeek::ValPtr");
|
||||||
public type BroType = __library_type("::zeek::TypePtr");
|
public type BroType = __library_type("::zeek::TypePtr");
|
||||||
public type EventHandlerPtr = __library_type("::zeek::EventHandlerPtr");
|
public type EventHandlerPtr = __library_type("::zeek::EventHandlerPtr");
|
||||||
public type PortRange = __library_type("::zeek::spicy::rt::PortRange");
|
public type PortRange = __library_type("::zeek::spicy::rt::PortRange");
|
||||||
|
public type RecordField = __library_type("::zeek::spicy::rt::RecordField");
|
||||||
|
|
||||||
declare public PortRange make_port_range(port begin_, port end_) &cxxname="zeek::spicy::rt::make_port_range" &have_prototype;
|
declare public PortRange make_port_range(port begin_, port end_) &cxxname="zeek::spicy::rt::make_port_range" &have_prototype;
|
||||||
|
|
||||||
|
@ -29,10 +30,10 @@ declare public void raise_event(EventHandlerPtr handler, vector<Val> args) &cxxn
|
||||||
declare public BroType event_arg_type(EventHandlerPtr handler, uint<64> idx) &cxxname="zeek::spicy::rt::event_arg_type" &have_prototype;
|
declare public BroType event_arg_type(EventHandlerPtr handler, uint<64> idx) &cxxname="zeek::spicy::rt::event_arg_type" &have_prototype;
|
||||||
declare public Val to_val(any x, BroType target) &cxxname="zeek::spicy::rt::to_val" &have_prototype;
|
declare public Val to_val(any x, BroType target) &cxxname="zeek::spicy::rt::to_val" &have_prototype;
|
||||||
|
|
||||||
type RecordField = tuple<string, BroType, bool>; # (ID, type, optional)
|
|
||||||
declare public BroType create_base_type(ZeekTypeTag tag) &cxxname="zeek::spicy::rt::create_base_type" &have_prototype;
|
declare public BroType create_base_type(ZeekTypeTag tag) &cxxname="zeek::spicy::rt::create_base_type" &have_prototype;
|
||||||
declare public BroType create_enum_type(string ns, string id, vector<tuple<string, int<64>>> labels) &cxxname="zeek::spicy::rt::create_enum_type" &have_prototype;
|
declare public BroType create_enum_type(string ns, string id, vector<tuple<string, int<64>>> labels) &cxxname="zeek::spicy::rt::create_enum_type" &have_prototype;
|
||||||
declare public BroType create_record_type(string ns, string id, vector<RecordField> fields) &cxxname="zeek::spicy::rt::create_record_type" &have_prototype;
|
declare public BroType create_record_type(string ns, string id, vector<RecordField> fields) &cxxname="zeek::spicy::rt::create_record_type" &have_prototype;
|
||||||
|
declare public RecordField create_record_field(string id, BroType type_, bool is_optional, bool is_log) &cxxname="zeek::spicy::rt::create_record_field" &have_prototype;
|
||||||
declare public BroType create_table_type(BroType key, optional<BroType> value = Null) &cxxname="zeek::spicy::rt::create_table_type" &have_prototype;
|
declare public BroType create_table_type(BroType key, optional<BroType> value = Null) &cxxname="zeek::spicy::rt::create_table_type" &have_prototype;
|
||||||
declare public BroType create_vector_type(BroType elem) &cxxname="zeek::spicy::rt::create_vector_type" &have_prototype;
|
declare public BroType create_vector_type(BroType elem) &cxxname="zeek::spicy::rt::create_vector_type" &have_prototype;
|
||||||
|
|
||||||
|
|
|
@ -126,20 +126,30 @@ TypePtr rt::create_record_type(const std::string& ns, const std::string& id,
|
||||||
|
|
||||||
auto decls = std::make_unique<type_decl_list>();
|
auto decls = std::make_unique<type_decl_list>();
|
||||||
|
|
||||||
for ( const auto& [id, type, optional] : fields ) {
|
for ( const auto& f : fields ) {
|
||||||
auto attrs = make_intrusive<detail::Attributes>(nullptr, true, false);
|
auto attrs = make_intrusive<detail::Attributes>(nullptr, true, false);
|
||||||
|
|
||||||
if ( optional ) {
|
if ( f.is_optional ) {
|
||||||
auto optional_ = make_intrusive<detail::Attr>(detail::ATTR_OPTIONAL);
|
auto optional_ = make_intrusive<detail::Attr>(detail::ATTR_OPTIONAL);
|
||||||
attrs->AddAttr(optional_);
|
attrs->AddAttr(std::move(optional_));
|
||||||
}
|
}
|
||||||
|
|
||||||
decls->append(new TypeDecl(util::copy_string(id.c_str()), type, std::move(attrs)));
|
if ( f.is_log ) {
|
||||||
|
auto log_ = make_intrusive<detail::Attr>(detail::ATTR_LOG);
|
||||||
|
attrs->AddAttr(std::move(log_));
|
||||||
|
}
|
||||||
|
|
||||||
|
decls->append(new TypeDecl(util::copy_string(f.id.c_str()), f.type, std::move(attrs)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return make_intrusive<RecordType>(decls.release());
|
return make_intrusive<RecordType>(decls.release());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rt::RecordField rt::create_record_field(const std::string& id, const TypePtr& type, hilti::rt::Bool is_optional,
|
||||||
|
hilti::rt::Bool is_log) {
|
||||||
|
return rt::RecordField{id, type, is_optional, is_log};
|
||||||
|
}
|
||||||
|
|
||||||
TypePtr rt::create_table_type(TypePtr key, std::optional<TypePtr> value) {
|
TypePtr rt::create_table_type(TypePtr key, std::optional<TypePtr> value) {
|
||||||
auto _ = hilti::rt::profiler::start("zeek/rt/create_table_type");
|
auto _ = hilti::rt::profiler::start("zeek/rt/create_table_type");
|
||||||
auto idx = make_intrusive<TypeList>();
|
auto idx = make_intrusive<TypeList>();
|
||||||
|
|
|
@ -151,9 +151,17 @@ extern TypePtr create_enum_type(
|
||||||
const std::string& ns, const std::string& id,
|
const std::string& ns, const std::string& id,
|
||||||
const hilti::rt::Vector<std::tuple<std::string, hilti::rt::integer::safe<int64_t>>>& labels);
|
const hilti::rt::Vector<std::tuple<std::string, hilti::rt::integer::safe<int64_t>>>& labels);
|
||||||
|
|
||||||
using RecordField = std::tuple<std::string, TypePtr, hilti::rt::Bool>; // (ID, type, optional)
|
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,
|
extern TypePtr create_record_type(const std::string& ns, const std::string& id,
|
||||||
const hilti::rt::Vector<RecordField>& fields);
|
const hilti::rt::Vector<RecordField>& 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<TypePtr> value);
|
extern TypePtr create_table_type(TypePtr key, std::optional<TypePtr> value);
|
||||||
extern TypePtr create_vector_type(const TypePtr& elem);
|
extern TypePtr create_vector_type(const TypePtr& elem);
|
||||||
|
|
|
@ -222,11 +222,16 @@ std::vector<TypeInfo> Driver::types() const {
|
||||||
std::vector<std::pair<TypeInfo, hilti::ID>> Driver::exportedTypes() const {
|
std::vector<std::pair<TypeInfo, hilti::ID>> Driver::exportedTypes() const {
|
||||||
std::vector<std::pair<TypeInfo, hilti::ID>> result;
|
std::vector<std::pair<TypeInfo, hilti::ID>> result;
|
||||||
|
|
||||||
for ( const auto& [spicy_id, zeek_id, _] : _glue->exportedIDs() ) {
|
for ( const auto& i : _glue->exportedIDs() ) {
|
||||||
if ( auto t = _types.find(spicy_id); t != _types.end() )
|
const auto& export_ = i.second;
|
||||||
result.emplace_back(t->second, zeek_id);
|
if ( auto t = _types.find(export_.spicy_id); t != _types.end() ) {
|
||||||
|
if ( ! export_.validate(t->second) )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
result.emplace_back(t->second, export_.zeek_id);
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
hilti::logger().error(hilti::rt::fmt("unknown type '%s' exported", spicy_id));
|
hilti::logger().error(hilti::rt::fmt("unknown type '%s' exported", export_.spicy_id));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -492,22 +492,11 @@ bool GlueCompiler::loadEvtFile(hilti::rt::filesystem::path& path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
else if ( looking_at(*chunk, 0, "export") ) {
|
else if ( looking_at(*chunk, 0, "export") ) {
|
||||||
size_t i = 0;
|
auto export_ = parseExport(*chunk);
|
||||||
eat_token(*chunk, &i, "export");
|
if ( _exports.find(export_.zeek_id) != _exports.end() )
|
||||||
|
throw ParseError(hilti::util::fmt("export of '%s' already defined", export_.zeek_id));
|
||||||
|
|
||||||
hilti::ID spicy_id = extract_id(*chunk, &i);
|
_exports[export_.zeek_id] = export_;
|
||||||
hilti::ID zeek_id = spicy_id;
|
|
||||||
|
|
||||||
if ( looking_at(*chunk, i, "as") ) {
|
|
||||||
eat_token(*chunk, &i, "as");
|
|
||||||
zeek_id = extract_id(*chunk, &i);
|
|
||||||
}
|
|
||||||
|
|
||||||
eat_spaces(*chunk, &i);
|
|
||||||
if ( ! looking_at(*chunk, i, ";") )
|
|
||||||
throw ParseError("syntax error in export");
|
|
||||||
|
|
||||||
_exports.emplace_back(std::move(spicy_id), std::move(zeek_id), _locations.back());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
|
@ -528,6 +517,70 @@ bool GlueCompiler::loadEvtFile(hilti::rt::filesystem::path& path) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<glue::Export> GlueCompiler::exportForZeekID(const hilti::ID& id) const {
|
||||||
|
if ( auto i = _exports.find(id); i != _exports.end() )
|
||||||
|
return i->second;
|
||||||
|
else
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
GlueCompiler::ExportedField GlueCompiler::exportForField(const hilti::ID& zeek_id, const hilti::ID& field_id) const {
|
||||||
|
ExportedField field;
|
||||||
|
|
||||||
|
auto export_ = exportForZeekID(zeek_id);
|
||||||
|
if ( ! export_ )
|
||||||
|
// No `export` for this type, return defaults.
|
||||||
|
return field;
|
||||||
|
|
||||||
|
if ( export_->with.empty() ) {
|
||||||
|
// Include unless explicitly excluded.
|
||||||
|
if ( export_->without.find(field_id) != export_->without.end() )
|
||||||
|
field.skip = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Exclude unless explicitly included.
|
||||||
|
if ( export_->with.find(field_id) == export_->with.end() )
|
||||||
|
field.skip = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( export_->log_all )
|
||||||
|
field.log = true;
|
||||||
|
|
||||||
|
if ( export_->logs.find(field_id) != export_->logs.end() )
|
||||||
|
field.log = true;
|
||||||
|
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool glue::Export::validate(const TypeInfo& ti) const {
|
||||||
|
auto utype = ti.type.tryAs<::spicy::type::Unit>();
|
||||||
|
if ( ! utype )
|
||||||
|
return true;
|
||||||
|
|
||||||
|
auto check_field_names = [&](const auto& fields) {
|
||||||
|
for ( const auto& f : fields ) {
|
||||||
|
if ( ! utype->itemByName(f) ) {
|
||||||
|
hilti::logger().error(hilti::rt::fmt("type '%s' does not have field '%s'", ti.id, f), ti.location);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
if ( ! check_field_names(with) )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if ( ! check_field_names(without) )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if ( ! check_field_names(logs) )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void GlueCompiler::addSpicyModule(const hilti::ID& id, const hilti::rt::filesystem::path& file) {
|
void GlueCompiler::addSpicyModule(const hilti::ID& id, const hilti::rt::filesystem::path& file) {
|
||||||
glue::SpicyModule module;
|
glue::SpicyModule module;
|
||||||
module.id = id;
|
module.id = id;
|
||||||
|
@ -796,6 +849,70 @@ glue::Event GlueCompiler::parseEvent(const std::string& chunk) {
|
||||||
return ev;
|
return ev;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
glue::Export GlueCompiler::parseExport(const std::string& chunk) {
|
||||||
|
glue::Export export_;
|
||||||
|
|
||||||
|
size_t i = 0;
|
||||||
|
eat_token(chunk, &i, "export");
|
||||||
|
|
||||||
|
export_.spicy_id = extract_id(chunk, &i);
|
||||||
|
export_.zeek_id = export_.spicy_id;
|
||||||
|
export_.location = _locations.back();
|
||||||
|
|
||||||
|
if ( looking_at(chunk, i, "as") ) {
|
||||||
|
eat_token(chunk, &i, "as");
|
||||||
|
export_.zeek_id = extract_id(chunk, &i);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( looking_at(chunk, i, "&log") ) {
|
||||||
|
eat_token(chunk, &i, "&log");
|
||||||
|
export_.log_all = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool expect_fields = false;
|
||||||
|
bool include_fields;
|
||||||
|
|
||||||
|
if ( looking_at(chunk, i, "without") ) {
|
||||||
|
eat_token(chunk, &i, "without");
|
||||||
|
include_fields = false;
|
||||||
|
expect_fields = true;
|
||||||
|
}
|
||||||
|
else if ( looking_at(chunk, i, "with") ) {
|
||||||
|
eat_token(chunk, &i, "with");
|
||||||
|
include_fields = true;
|
||||||
|
expect_fields = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( expect_fields ) {
|
||||||
|
eat_token(chunk, &i, "{");
|
||||||
|
|
||||||
|
while ( true ) {
|
||||||
|
auto field = extract_id(chunk, &i);
|
||||||
|
if ( include_fields )
|
||||||
|
export_.with.insert(field);
|
||||||
|
else
|
||||||
|
export_.without.insert(field);
|
||||||
|
|
||||||
|
if ( looking_at(chunk, i, "&log") ) {
|
||||||
|
eat_token(chunk, &i, "&log");
|
||||||
|
export_.logs.insert(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( looking_at(chunk, i, "}") ) {
|
||||||
|
eat_token(chunk, &i, "}");
|
||||||
|
break; // All done.
|
||||||
|
}
|
||||||
|
|
||||||
|
eat_token(chunk, &i, ",");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! looking_at(chunk, i, ";") )
|
||||||
|
throw ParseError("syntax error in export");
|
||||||
|
|
||||||
|
return export_;
|
||||||
|
}
|
||||||
|
|
||||||
bool GlueCompiler::compile() {
|
bool GlueCompiler::compile() {
|
||||||
assert(_driver);
|
assert(_driver);
|
||||||
|
|
||||||
|
@ -1218,6 +1335,12 @@ struct VisitorZeekType : hilti::visitor::PreOrder<hilti::Result<hilti::Expressio
|
||||||
{builder::string(ns), builder::string(local), builder::vector(fields)});
|
{builder::string(ns), builder::string(local), builder::vector(fields)});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hilti::Expression create_record_field(const hilti::ID& id, const hilti::Expression& type, bool optional,
|
||||||
|
bool log) const {
|
||||||
|
return builder::call("zeek_rt::create_record_field",
|
||||||
|
{builder::string(id), type, builder::bool_(optional), builder::bool_(log)});
|
||||||
|
}
|
||||||
|
|
||||||
result_t base_type(const char* tag) { return builder::call("zeek_rt::create_base_type", {builder::id(tag)}); }
|
result_t base_type(const char* tag) { return builder::call("zeek_rt::create_base_type", {builder::id(tag)}); }
|
||||||
|
|
||||||
result_t createZeekType(const hilti::Type& t, const std::optional<hilti::ID>& id_ = {}) {
|
result_t createZeekType(const hilti::Type& t, const std::optional<hilti::ID>& id_ = {}) {
|
||||||
|
@ -1302,7 +1425,7 @@ struct VisitorZeekType : hilti::visitor::PreOrder<hilti::Result<hilti::Expressio
|
||||||
if ( ! ztype )
|
if ( ! ztype )
|
||||||
return ztype.error();
|
return ztype.error();
|
||||||
|
|
||||||
fields.emplace_back(builder::tuple({builder::string(f.id()), *ztype, builder::bool_(f.isOptional())}));
|
fields.emplace_back(create_record_field(f.id(), *ztype, f.isOptional(), false));
|
||||||
}
|
}
|
||||||
|
|
||||||
return create_record_type(id()->namespace_(), id()->local(), fields);
|
return create_record_type(id()->namespace_(), id()->local(), fields);
|
||||||
|
@ -1318,7 +1441,7 @@ struct VisitorZeekType : hilti::visitor::PreOrder<hilti::Result<hilti::Expressio
|
||||||
if ( ! ztype )
|
if ( ! ztype )
|
||||||
return ztype.error();
|
return ztype.error();
|
||||||
|
|
||||||
fields.emplace_back(builder::tuple({builder::string(*f.id()), *ztype, builder::bool_(false)}));
|
fields.emplace_back(create_record_field(*f.id(), *ztype, false, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
hilti::ID local;
|
hilti::ID local;
|
||||||
|
@ -1347,12 +1470,16 @@ struct VisitorZeekType : hilti::visitor::PreOrder<hilti::Result<hilti::Expressio
|
||||||
|
|
||||||
std::vector<hilti::Expression> fields;
|
std::vector<hilti::Expression> fields;
|
||||||
for ( const auto& f : gc->recordFields(t) ) {
|
for ( const auto& f : gc->recordFields(t) ) {
|
||||||
auto ztype = createZeekType(std::get<1>(f));
|
auto export_ = gc->exportForField(*id(), hilti::ID(f.id));
|
||||||
|
|
||||||
|
if ( export_.skip )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto ztype = createZeekType(f.type);
|
||||||
if ( ! ztype )
|
if ( ! ztype )
|
||||||
return ztype.error();
|
return ztype.error();
|
||||||
|
|
||||||
fields.emplace_back(
|
fields.emplace_back(create_record_field(f.id, *ztype, f.is_optional, export_.log));
|
||||||
builder::tuple({builder::string(std::get<0>(f)), *ztype, builder::bool_(std::get<2>(f))}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return create_record_type(id()->namespace_(), id()->local(), fields);
|
return create_record_type(id()->namespace_(), id()->local(), fields);
|
||||||
|
@ -1382,11 +1509,13 @@ struct VisitorUnitFields : hilti::visitor::PreOrder<void, VisitorUnitFields> {
|
||||||
if ( f.isTransient() || f.parseType().isA<hilti::type::Void>() )
|
if ( f.isTransient() || f.parseType().isA<hilti::type::Void>() )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
fields.emplace_back(f.id(), f.itemType(), true);
|
auto field = GlueCompiler::RecordField{.id = f.id(), .type = f.itemType(), .is_optional = true};
|
||||||
|
fields.emplace_back(std::move(field));
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(const ::spicy::type::unit::item::Variable& f, const position_t p) {
|
void operator()(const ::spicy::type::unit::item::Variable& f, const position_t p) {
|
||||||
fields.emplace_back(f.id(), f.itemType(), f.isOptional());
|
auto field = GlueCompiler::RecordField{.id = f.id(), .type = f.itemType(), .is_optional = f.isOptional()};
|
||||||
|
fields.emplace_back(std::move(field));
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(const ::spicy::type::unit::item::Switch& f, const position_t p) {
|
void operator()(const ::spicy::type::unit::item::Switch& f, const position_t p) {
|
||||||
|
|
|
@ -131,6 +131,25 @@ struct Event {
|
||||||
std::vector<ExpressionAccessor> expression_accessors; /**< One HILTI function per expression to access the value. */
|
std::vector<ExpressionAccessor> expression_accessors; /**< One HILTI function per expression to access the value. */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Representation of an "export" statement parsed from an EVT file. */
|
||||||
|
struct Export {
|
||||||
|
hilti::ID spicy_id;
|
||||||
|
hilti::ID zeek_id;
|
||||||
|
hilti::Location location;
|
||||||
|
|
||||||
|
// Additional information for exported record types.
|
||||||
|
bool log_all = false; /**< mark all fields for logging in exported record */
|
||||||
|
std::set<hilti::ID> with; /**< fields to include in exported record */
|
||||||
|
std::set<hilti::ID> without; /**< fields to exclude from exported record */
|
||||||
|
std::set<hilti::ID> logs; /**< fields to mark for logging in exported record */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks that the information is semantically correct given the provided
|
||||||
|
* type information. Logs any errors and returns false on failure.
|
||||||
|
**/
|
||||||
|
bool validate(const TypeInfo& ti) const;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace glue
|
} // namespace glue
|
||||||
|
|
||||||
/** Generates the glue code between Zeek and Spicy based on *.evt files. */
|
/** Generates the glue code between Zeek and Spicy based on *.evt files. */
|
||||||
|
@ -163,10 +182,31 @@ public:
|
||||||
/** Returns all IDs that have been exported so far. */
|
/** Returns all IDs that have been exported so far. */
|
||||||
const auto& exportedIDs() const { return _exports; }
|
const auto& exportedIDs() const { return _exports; }
|
||||||
|
|
||||||
|
/** Returns the `export` declaration for a specific type given by the Zeek-side ID, if available. */
|
||||||
|
std::optional<glue::Export> exportForZeekID(const hilti::ID& id) const;
|
||||||
|
|
||||||
|
/** Provides `export` details for a given record field. */
|
||||||
|
struct ExportedField {
|
||||||
|
bool skip = false; /**< True if field is not to be included in the exported type. */
|
||||||
|
bool log = false; /**< True if field is logged. */
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the `export` details for a given record field. If our EVT
|
||||||
|
* file doesn't mention the field explicitly, the method returns the
|
||||||
|
* default behavior.
|
||||||
|
*/
|
||||||
|
ExportedField exportForField(const hilti::ID& zeek_id, const hilti::ID& field_id) const;
|
||||||
|
|
||||||
/** Generates code to convert a HILTI type to a corresponding Zeek type at runtime. */
|
/** Generates code to convert a HILTI type to a corresponding Zeek type at runtime. */
|
||||||
hilti::Result<hilti::Expression> createZeekType(const hilti::Type& t, const hilti::ID& id) const;
|
hilti::Result<hilti::Expression> createZeekType(const hilti::Type& t, const hilti::ID& id) const;
|
||||||
|
|
||||||
using RecordField = std::tuple<std::string, hilti::Type, bool>; /**< (ID, type, optional) */
|
/** Return type for `recordField()`. */
|
||||||
|
struct RecordField {
|
||||||
|
hilti::ID id; /**< name of record field */
|
||||||
|
hilti::Type type; /**< Spicy-side type object */
|
||||||
|
bool is_optional; /**< true if field is optional */
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to retrieve a list of Zeek-side record fields that converting a
|
* Helper to retrieve a list of Zeek-side record fields that converting a
|
||||||
|
@ -205,6 +245,7 @@ private:
|
||||||
glue::FileAnalyzer parseFileAnalyzer(const std::string& chunk);
|
glue::FileAnalyzer parseFileAnalyzer(const std::string& chunk);
|
||||||
glue::PacketAnalyzer parsePacketAnalyzer(const std::string& chunk);
|
glue::PacketAnalyzer parsePacketAnalyzer(const std::string& chunk);
|
||||||
glue::Event parseEvent(const std::string& chunk);
|
glue::Event parseEvent(const std::string& chunk);
|
||||||
|
glue::Export parseExport(const std::string& chunk);
|
||||||
|
|
||||||
/** Computes the missing pieces for all `Event` instances. */
|
/** Computes the missing pieces for all `Event` instances. */
|
||||||
bool PopulateEvents();
|
bool PopulateEvents();
|
||||||
|
@ -221,9 +262,9 @@ private:
|
||||||
std::map<hilti::ID, std::shared_ptr<glue::SpicyModule>> _spicy_modules;
|
std::map<hilti::ID, std::shared_ptr<glue::SpicyModule>> _spicy_modules;
|
||||||
|
|
||||||
std::vector<std::pair<hilti::ID, std::optional<hilti::ID>>>
|
std::vector<std::pair<hilti::ID, std::optional<hilti::ID>>>
|
||||||
_imports; /**< imports from EVT files, with ID and optional scope */
|
_imports; /**< imports from EVT files, with ID and optional scope */
|
||||||
std::vector<std::tuple<hilti::ID, hilti::ID, hilti::Location>> _exports; /**< exports from EVT files */
|
std::map<hilti::ID, glue::Export> _exports; /**< exports from EVT files */
|
||||||
std::vector<glue::Event> _events; /**< events parsed from EVT files */
|
std::vector<glue::Event> _events; /**< events parsed from EVT files */
|
||||||
std::vector<glue::ProtocolAnalyzer> _protocol_analyzers; /**< protocol analyzers parsed from EVT files */
|
std::vector<glue::ProtocolAnalyzer> _protocol_analyzers; /**< protocol analyzers parsed from EVT files */
|
||||||
std::vector<glue::FileAnalyzer> _file_analyzers; /**< file analyzers parsed from EVT files */
|
std::vector<glue::FileAnalyzer> _file_analyzers; /**< file analyzers parsed from EVT files */
|
||||||
std::vector<glue::PacketAnalyzer> _packet_analyzers; /**< file analyzers parsed from EVT files */
|
std::vector<glue::PacketAnalyzer> _packet_analyzers; /**< file analyzers parsed from EVT files */
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||||
|
[error] <...>/foo.evt:3: export of 'Test::A' already defined
|
||||||
|
[error] error loading EVT file "<...>/foo.evt"
|
|
@ -1,6 +1,6 @@
|
||||||
### 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.
|
||||||
[error] unknown type 'Test::DOES_NOT_EXIST' exported
|
|
||||||
[error] unknown type 'NOT_SCOPED' exported
|
[error] unknown type 'NOT_SCOPED' exported
|
||||||
|
[error] unknown type 'Test::DOES_NOT_EXIST' exported
|
||||||
[error] <...>/foo.spicy:1:13-5:3: cannot export Spicy type 'Test::X': type is self-recursive
|
[error] <...>/foo.spicy:1:13-5:3: cannot export Spicy type 'Test::X': type is self-recursive
|
||||||
[error] <...>/foo.spicy:9:3-13:3: cannot export Spicy type 'Test::Z': can only convert tuple types with all-named fields to Zeek
|
[error] <...>/foo.spicy:9:3-13:3: cannot export Spicy type 'Test::Z': can only convert tuple types with all-named fields to Zeek
|
||||||
[error] <Spicy Plugin for Zeek>: aborting after errors
|
[error] <Spicy Plugin for Zeek>: aborting after errors
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||||
|
[error] <...>/foo.spicy:1:13-5:3: type 'Test::A' does not have field 'does_not_exist'
|
||||||
|
[error] <...>/foo.spicy:1:13-5:3: type 'Test::A' does not have field 'does_not_exist'
|
||||||
|
[error] <Spicy Plugin for Zeek>: aborting after errors
|
16
testing/btest/Baseline/spicy.export-type-with-fields/output
Normal file
16
testing/btest/Baseline/spicy.export-type-with-fields/output
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||||
|
=== X
|
||||||
|
name=x log=F
|
||||||
|
=== X1
|
||||||
|
name=x log=F
|
||||||
|
name=z log=F
|
||||||
|
name=y log=F
|
||||||
|
=== X2
|
||||||
|
name=x log=T
|
||||||
|
name=z log=T
|
||||||
|
name=y log=T
|
||||||
|
=== X3
|
||||||
|
name=x log=F
|
||||||
|
name=z log=T
|
||||||
|
=== X4
|
||||||
|
name=z log=F
|
25
testing/btest/spicy/export-type-ambigious-fail.spicy
Normal file
25
testing/btest/spicy/export-type-ambigious-fail.spicy
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# @TEST-REQUIRES: have-spicy
|
||||||
|
#
|
||||||
|
# @TEST-DOC: Fail attempt to export a type multiple times
|
||||||
|
#
|
||||||
|
# @TEST-EXEC-FAIL: spicyz -d foo.spicy foo.evt -o foo.hlto >output 2>&1
|
||||||
|
# @TEST-EXEC: TEST_DIFF_CANONIFIER=diff-canonifier-spicy btest-diff output
|
||||||
|
|
||||||
|
# @TEST-START-FILE foo.spicy
|
||||||
|
module Test;
|
||||||
|
|
||||||
|
type A = unit {
|
||||||
|
x: uint8;
|
||||||
|
};
|
||||||
|
|
||||||
|
# @TEST-END-FILE
|
||||||
|
|
||||||
|
# @TEST-START-FILE foo.evt
|
||||||
|
|
||||||
|
export Test::A with { x };
|
||||||
|
export Test::A without { x };
|
||||||
|
|
||||||
|
# @TEST-END-FILE
|
||||||
|
|
||||||
|
# @TEST-START-FILE foo.zeek
|
||||||
|
# @TEST-END-FILE
|
25
testing/btest/spicy/export-type-with-fields-fail.spicy
Normal file
25
testing/btest/spicy/export-type-with-fields-fail.spicy
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# @TEST-REQUIRES: have-spicy
|
||||||
|
#
|
||||||
|
# @TEST-DOC: Failure cases for `export` with field specifcations.
|
||||||
|
#
|
||||||
|
# @TEST-EXEC-FAIL: spicyz -d foo.spicy foo.evt -o foo.hlto >output 2>&1
|
||||||
|
# @TEST-EXEC: TEST_DIFF_CANONIFIER=diff-canonifier-spicy btest-diff output
|
||||||
|
|
||||||
|
# @TEST-START-FILE foo.spicy
|
||||||
|
module Test;
|
||||||
|
|
||||||
|
type A = unit {
|
||||||
|
x: uint8;
|
||||||
|
};
|
||||||
|
|
||||||
|
# @TEST-END-FILE
|
||||||
|
|
||||||
|
# @TEST-START-FILE foo.evt
|
||||||
|
|
||||||
|
export Test::A as Test::A1 with { does_not_exist };
|
||||||
|
export Test::A as Test::A2 without { does_not_exist };
|
||||||
|
|
||||||
|
# @TEST-END-FILE
|
||||||
|
|
||||||
|
# @TEST-START-FILE foo.zeek
|
||||||
|
# @TEST-END-FILE
|
48
testing/btest/spicy/export-type-with-fields.zeek
Normal file
48
testing/btest/spicy/export-type-with-fields.zeek
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
# @TEST-REQUIRES: have-spicy
|
||||||
|
#
|
||||||
|
# @TEST-EXEC: spicyz -do export.hlto export.spicy export.evt
|
||||||
|
# @TEST-EXEC: zeek export.hlto %INPUT >>output
|
||||||
|
# @TEST-EXEC: btest-diff output
|
||||||
|
#
|
||||||
|
# @TEST-DOC: Test type export with specified fields.
|
||||||
|
|
||||||
|
# @TEST-START-FILE export.spicy
|
||||||
|
module foo;
|
||||||
|
|
||||||
|
public type X = unit {
|
||||||
|
x: uint8;
|
||||||
|
y: uint8;
|
||||||
|
z: uint8;
|
||||||
|
};
|
||||||
|
# @TEST-END-FILE
|
||||||
|
|
||||||
|
# @TEST-START-FILE export.evt
|
||||||
|
import foo;
|
||||||
|
|
||||||
|
protocol analyzer FOO over TCP:
|
||||||
|
parse with foo::X,
|
||||||
|
port 80/tcp;
|
||||||
|
|
||||||
|
export foo::X with { x };
|
||||||
|
export foo::X as foo::X1;
|
||||||
|
export foo::X as foo::X2 &log;
|
||||||
|
export foo::X as foo::X3 with { x, z &log };
|
||||||
|
export foo::X as foo::X4 without { x, y };
|
||||||
|
|
||||||
|
# @TEST-END-FILE
|
||||||
|
|
||||||
|
function printFields(name: string, t: any) {
|
||||||
|
print fmt("=== %s", name);
|
||||||
|
local fields = record_fields(t);
|
||||||
|
for ( f in fields )
|
||||||
|
print fmt("name=%s log=%s", f, fields[f]$log);
|
||||||
|
}
|
||||||
|
|
||||||
|
event zeek_init() {
|
||||||
|
printFields("X ", foo::X);
|
||||||
|
printFields("X1", foo::X1);
|
||||||
|
printFields("X2", foo::X2);
|
||||||
|
printFields("X3", foo::X3);
|
||||||
|
printFields("X4", foo::X4);
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue