// See the file "COPYING" in the main distribution directory for copyright. #include "zeek/EventTrace.h" #include #include "zeek/Desc.h" #include "zeek/EventHandler.h" #include "zeek/Func.h" #include "zeek/IPAddr.h" #include "zeek/Reporter.h" #include "zeek/ZeekString.h" namespace zeek::detail { std::unique_ptr event_trace_mgr; // Helper function for generating a correct script-level representation // of a string constant. static std::string escape_string(const u_char* b, int len) { std::string res = "\""; for ( int i = 0; i < len; ++i ) { unsigned char c = b[i]; switch ( c ) { case '\a': res += "\\a"; break; case '\b': res += "\\b"; break; case '\f': res += "\\f"; break; case '\n': res += "\\n"; break; case '\r': res += "\\r"; break; case '\t': res += "\\t"; break; case '\v': res += "\\v"; break; case '\\': res += "\\\\"; break; case '"': res += "\\\""; break; default: if ( isprint(c) ) res += c; else { char buf[8192]; snprintf(buf, sizeof buf, "%03o", c); res += "\\"; res += buf; } break; } } return res + "\""; } ValTrace::ValTrace(ValPtr _v) : v(std::move(_v)) { t = v->GetType(); switch ( t->Tag() ) { case TYPE_LIST: TraceList(cast_intrusive(v)); break; case TYPE_RECORD: TraceRecord(cast_intrusive(v)); break; case TYPE_TABLE: TraceTable(cast_intrusive(v)); break; case TYPE_VECTOR: TraceVector(cast_intrusive(v)); break; default: break; } } bool ValTrace::operator==(const ValTrace& vt) const { auto& vt_v = vt.GetVal(); if ( vt_v == v ) return true; auto tag = t->Tag(); if ( vt.GetType()->Tag() != tag ) return false; switch ( tag ) { case TYPE_BOOL: case TYPE_INT: case TYPE_ENUM: return v->AsInt() == vt_v->AsInt(); case TYPE_COUNT: case TYPE_PORT: return v->AsCount() == vt_v->AsCount(); case TYPE_DOUBLE: case TYPE_INTERVAL: case TYPE_TIME: return v->AsDouble() == vt_v->AsDouble(); case TYPE_STRING: return (*v->AsString()) == (*vt_v->AsString()); case TYPE_ADDR: return v->AsAddr() == vt_v->AsAddr(); case TYPE_SUBNET: return v->AsSubNet() == vt_v->AsSubNet(); case TYPE_FUNC: return v->AsFunc() == vt_v->AsFunc(); case TYPE_FILE: return v->AsFile() == vt_v->AsFile(); case TYPE_PATTERN: return v->AsPattern() == vt_v->AsPattern(); case TYPE_ANY: return v->AsSubNet() == vt_v->AsSubNet(); case TYPE_TYPE: return v->AsType() == vt_v->AsType(); case TYPE_OPAQUE: return false; // needs pointer equivalence case TYPE_LIST: return SameList(vt); case TYPE_RECORD: return SameRecord(vt); case TYPE_TABLE: return SameTable(vt); case TYPE_VECTOR: return SameVector(vt); default: reporter->InternalError("bad type in ValTrace::operator=="); } } void ValTrace::ComputeDelta(const ValTrace* prev, DeltaVector& deltas) const { auto tag = t->Tag(); if ( prev ) { ASSERT(prev->GetType()->Tag() == tag); auto& prev_v = prev->GetVal(); if ( prev_v != v ) { if ( *this != *prev ) deltas.emplace_back(std::make_unique(this, v)); return; } } switch ( tag ) { case TYPE_BOOL: case TYPE_INT: case TYPE_ENUM: case TYPE_COUNT: case TYPE_PORT: case TYPE_DOUBLE: case TYPE_INTERVAL: case TYPE_TIME: case TYPE_STRING: case TYPE_ADDR: case TYPE_SUBNET: case TYPE_FUNC: case TYPE_PATTERN: case TYPE_TYPE: // These don't change in place. No need to create // them as stand-alone variables, since we can just // use the constant representation instead. break; case TYPE_ANY: case TYPE_FILE: case TYPE_OPAQUE: // If we have a previous instance, we can ignore this // one, because we know it's equivalent (due to the // test at the beginning of this method), and it's // not meaningful to recurse inside it looking for // interior changes. if ( ! prev ) deltas.emplace_back(std::make_unique(this)); break; case TYPE_LIST: // We shouldn't see these exposed directly, as they're // not manipulable at script-level. An exception // might be for "any" types that are then decomposed // via compound assignment; for now, we don't support // those. reporter->InternalError("list type seen in ValTrace::ComputeDelta"); break; case TYPE_RECORD: if ( prev ) ComputeRecordDelta(prev, deltas); else deltas.emplace_back(std::make_unique(this)); break; case TYPE_TABLE: if ( prev ) ComputeTableDelta(prev, deltas); else if ( GetType()->AsTableType()->IsUnspecifiedTable() ) // For unspecified values, we generate them // as empty constructors, because we don't // know their yield type and thus can't // create variables corresponding to them. break; else if ( t->Yield() ) deltas.emplace_back(std::make_unique(this)); else deltas.emplace_back(std::make_unique(this)); break; case TYPE_VECTOR: if ( prev ) ComputeVectorDelta(prev, deltas); else if ( GetType()->AsVectorType()->IsUnspecifiedVector() ) // See above for empty tables/sets. break; else deltas.emplace_back(std::make_unique(this)); break; default: reporter->InternalError("bad type in ValTrace::ComputeDelta"); } } void ValTrace::TraceList(const ListValPtr& lv) { auto vals = lv->Vals(); for ( auto& v : vals ) elems.emplace_back(std::make_shared(v)); } void ValTrace::TraceRecord(const RecordValPtr& rv) { auto n = rv->NumFields(); auto rt = rv->GetType(); for ( auto i = 0U; i < n; ++i ) { auto f = rv->RawOptField(i); if ( f ) { auto val = f->ToVal(rt->GetFieldType(i)); elems.emplace_back(std::make_shared(val)); } else elems.emplace_back(nullptr); } } void ValTrace::TraceTable(const TableValPtr& tv) { for ( auto& elem : tv->ToMap() ) { auto& key = elem.first; elems.emplace_back(std::make_shared(key)); auto& val = elem.second; if ( val ) elems2.emplace_back(std::make_shared(val)); } } void ValTrace::TraceVector(const VectorValPtr& vv) { auto& vec = vv->RawVec(); auto n = vec.size(); auto& yt = vv->RawYieldType(); auto& yts = vv->RawYieldTypes(); for ( auto i = 0U; i < n; ++i ) { auto& elem = vec[i]; if ( elem ) { auto& t = yts ? (*yts)[i] : yt; auto val = elem->ToVal(t); elems.emplace_back(std::make_shared(val)); } else elems.emplace_back(nullptr); } } bool ValTrace::SameList(const ValTrace& vt) const { return SameElems(vt); } bool ValTrace::SameRecord(const ValTrace& vt) const { return SameElems(vt); } bool ValTrace::SameTable(const ValTrace& vt) const { auto& vt_elems = vt.elems; auto n = elems.size(); if ( n != vt_elems.size() ) return false; auto& vt_elems2 = vt.elems2; auto n2 = elems2.size(); if ( n2 != vt_elems2.size() ) return false; ASSERT(n2 == 0 || n == n2); // We accommodate the possibility that keys are out-of-order // between the two sets of elements. // The following is O(N^2), but presumably if tables are somehow // involved (in fact we can only get here if they're used as // indices into other tables), then they'll likely be small. for ( auto i = 0U; i < n; ++i ) { auto& elem_i = elems[i]; // See if we can find a match for it. If we do, we don't // have to worry that another entry matched it too, since // all table/set indices will be distinct. auto j = 0U; for ( ; j < n; ++j ) { auto& vt_elem_j = vt_elems[j]; if ( *elem_i == *vt_elem_j ) break; } if ( j == n ) // No match for the index. return false; if ( n2 > 0 ) { // Need a match for the corresponding yield values. if ( *elems2[i] != *vt_elems2[j] ) return false; } } return true; } bool ValTrace::SameVector(const ValTrace& vt) const { return SameElems(vt); } bool ValTrace::SameElems(const ValTrace& vt) const { auto& vt_elems = vt.elems; auto n = elems.size(); if ( n != vt_elems.size() ) return false; for ( auto i = 0U; i < n; ++i ) { auto& trace_i = elems[i]; auto& vt_trace_i = vt_elems[i]; if ( trace_i && vt_trace_i ) { if ( *trace_i != *vt_trace_i ) return false; } else if ( trace_i || vt_trace_i ) return false; } return true; } bool ValTrace::SameSingleton(const ValTrace& vt) const { return ! IsAggr(t) && *this == vt; } void ValTrace::ComputeRecordDelta(const ValTrace* prev, DeltaVector& deltas) const { auto& prev_elems = prev->elems; auto n = elems.size(); if ( n != prev_elems.size() ) reporter->InternalError("size inconsistency in ValTrace::ComputeRecordDelta"); for ( auto i = 0U; i < n; ++i ) { const auto trace_i = elems[i].get(); const auto prev_trace_i = prev_elems[i].get(); if ( trace_i ) { if ( prev_trace_i ) { auto& v = trace_i->GetVal(); auto& prev_v = prev_trace_i->GetVal(); if ( v == prev_v ) { trace_i->ComputeDelta(prev_trace_i, deltas); continue; } if ( trace_i->SameSingleton(*prev_trace_i) ) // No further work needed. continue; } deltas.emplace_back(std::make_unique(this, i, trace_i->GetVal())); } else if ( prev_trace_i ) deltas.emplace_back(std::make_unique(this, i)); } } void ValTrace::ComputeTableDelta(const ValTrace* prev, DeltaVector& deltas) const { auto& prev_elems = prev->elems; auto& prev_elems2 = prev->elems2; auto n = elems.size(); auto is_set = elems2.size() == 0; auto prev_n = prev_elems.size(); // We can't compare pointers for the indices because they're // new objects generated afresh by TableVal::ToMap. So we do // explicit full comparisons for equality, distinguishing values // newly added, common to both, or (implicitly) removed. We'll // then go through the common to check them further. // // Our approach is O(N^2), but presumably these tables aren't // large, and in any case generating event traces is not something // requiring high performance, so we opt for conceptual simplicity. // Track which index values are newly added: std::set added_indices; // Track which entry traces are in common. Indexed by previous // trace elem index, yielding current trace elem index. std::map common_entries; for ( auto i = 0U; i < n; ++i ) { const auto trace_i = elems[i].get(); bool common = false; for ( auto j = 0U; j < prev_n; ++j ) { const auto prev_trace_j = prev_elems[j].get(); if ( *trace_i == *prev_trace_j ) { common_entries[j] = i; common = true; break; } } if ( ! common ) { auto v = trace_i->GetVal(); if ( is_set ) deltas.emplace_back(std::make_unique(this, v)); else { auto yield = elems2[i]->GetVal(); deltas.emplace_back(std::make_unique(this, v, yield)); } added_indices.insert(v.get()); } } for ( auto j = 0U; j < prev_n; ++j ) { auto common_pair = common_entries.find(j); if ( common_pair == common_entries.end() ) { auto& prev_trace = prev_elems[j]; auto& v = prev_trace->GetVal(); deltas.emplace_back(std::make_unique(this, v)); continue; } if ( is_set ) continue; // If we get here, we're analyzing a table for which there's // a common index. The remaining question is whether the // yield has changed. auto i = common_pair->second; auto& trace2 = elems2[i]; const auto prev_trace2 = prev_elems2[j]; auto& yield = trace2->GetVal(); auto& prev_yield = prev_trace2->GetVal(); if ( yield == prev_yield ) // Same yield, look for differences in its sub-elements. trace2->ComputeDelta(prev_trace2.get(), deltas); else if ( ! trace2->SameSingleton(*prev_trace2) ) deltas.emplace_back(std::make_unique(this, elems[i]->GetVal(), yield)); } } void ValTrace::ComputeVectorDelta(const ValTrace* prev, DeltaVector& deltas) const { auto& prev_elems = prev->elems; auto n = elems.size(); auto prev_n = prev_elems.size(); // TODO: The following hasn't been tested for robustness to vector holes. if ( n < prev_n ) { // The vector shrank in size. Easiest to just build it // from scratch. deltas.emplace_back(std::make_unique(this)); return; } // Look for existing entries that need reassignment. auto i = 0U; for ( ; i < prev_n; ++i ) { const auto trace_i = elems[i].get(); const auto prev_trace_i = prev_elems[i].get(); auto& elem_i = trace_i->GetVal(); auto& prev_elem_i = prev_trace_i->GetVal(); if ( elem_i == prev_elem_i ) trace_i->ComputeDelta(prev_trace_i, deltas); else if ( ! trace_i->SameSingleton(*prev_trace_i) ) deltas.emplace_back(std::make_unique(this, i, elem_i)); } // Now append any new entries. for ( ; i < n; ++i ) { auto& trace_i = elems[i]; auto& elem_i = trace_i->GetVal(); deltas.emplace_back(std::make_unique(this, i, elem_i)); } } std::string ValDelta::Generate(ValTraceMgr* vtm) const { return ""; } std::string DeltaReplaceValue::Generate(ValTraceMgr* vtm) const { return std::string(" = ") + vtm->ValName(new_val); } std::string DeltaSetField::Generate(ValTraceMgr* vtm) const { auto rt = vt->GetType()->AsRecordType(); auto f = rt->FieldName(field); return std::string("$") + f + " = " + vtm->ValName(new_val); } std::string DeltaRemoveField::Generate(ValTraceMgr* vtm) const { auto rt = vt->GetType()->AsRecordType(); auto f = rt->FieldName(field); return std::string("delete ") + vtm->ValName(vt) + "$" + f; } std::string DeltaRecordCreate::Generate(ValTraceMgr* vtm) const { auto rv = cast_intrusive(vt->GetVal()); auto rt = rv->GetType(); auto n = rt->NumFields(); std::string args; for ( auto i = 0; i < n; ++i ) { auto v_i = rv->GetField(i); if ( v_i ) { if ( ! args.empty() ) args += ", "; args += std::string("$") + rt->FieldName(i) + "=" + vtm->ValName(v_i); } } auto name = rt->GetName(); if ( name.empty() ) name = "record"; return std::string(" = ") + name + "(" + args + ")"; } std::string DeltaSetSetEntry::Generate(ValTraceMgr* vtm) const { return std::string("add ") + vtm->ValName(vt) + "[" + vtm->ValName(index) + "]"; } std::string DeltaSetTableEntry::Generate(ValTraceMgr* vtm) const { return std::string("[") + vtm->ValName(index) + "] = " + vtm->ValName(new_val); } std::string DeltaRemoveTableEntry::Generate(ValTraceMgr* vtm) const { return std::string("delete ") + vtm->ValName(vt) + "[" + vtm->ValName(index) + "]"; } std::string DeltaSetCreate::Generate(ValTraceMgr* vtm) const { auto sv = cast_intrusive(vt->GetVal()); auto members = sv->ToMap(); std::string args; for ( auto& m : members ) { if ( ! args.empty() ) args += ", "; args += vtm->ValName(m.first); } auto name = sv->GetType()->GetName(); if ( name.empty() ) name = "set"; return std::string(" = ") + name + "(" + args + ")"; } std::string DeltaTableCreate::Generate(ValTraceMgr* vtm) const { auto tv = cast_intrusive(vt->GetVal()); auto members = tv->ToMap(); std::string args; for ( auto& m : members ) { if ( ! args.empty() ) args += ", "; args += std::string("[") + vtm->ValName(m.first) + "] = " + vtm->ValName(m.second); } auto name = tv->GetType()->GetName(); if ( name.empty() ) name = "table"; return std::string(" = ") + name + "(" + args + ")"; } std::string DeltaVectorSet::Generate(ValTraceMgr* vtm) const { return std::string("[") + std::to_string(index) + "] = " + vtm->ValName(elem); } std::string DeltaVectorAppend::Generate(ValTraceMgr* vtm) const { return std::string("[") + std::to_string(index) + "] = " + vtm->ValName(elem); } std::string DeltaVectorCreate::Generate(ValTraceMgr* vtm) const { auto& elems = vt->GetElems(); std::string vec; for ( auto& e : elems ) { if ( vec.size() > 0 ) vec += ", "; vec += vtm->ValName(e->GetVal()); } return std::string(" = vector(") + vec + ")"; } std::string DeltaUnsupportedCreate::Generate(ValTraceMgr* vtm) const { return " = UNSUPPORTED " + obj_desc_short(vt->GetVal()->GetType().get()); } EventTrace::EventTrace(const ScriptFunc* _ev, double _nt, size_t event_num) : ev(_ev), nt(_nt) { auto ev_name = std::regex_replace(ev->GetName(), std::regex(":"), "_"); name = ev_name + "_" + std::to_string(event_num) + "__et"; } void EventTrace::Generate(FILE* f, ValTraceMgr& vtm, const DeltaGenVec& dvec, std::string successor, int num_pre) const { int offset = 0; for ( auto& d : dvec ) { auto& val = d.GetVal(); if ( d.IsFirstDef() && vtm.IsGlobal(val) ) { auto& val_name = vtm.ValName(val); std::string type_name; auto& t = val->GetType(); auto& tn = t->GetName(); if ( tn.empty() ) { ODesc d; t->Describe(&d); type_name = d.Description(); } else type_name = tn; auto anno = offset < num_pre ? " # from script" : ""; fprintf(f, "global %s: %s;%s\n", val_name.c_str(), type_name.c_str(), anno); } ++offset; } fprintf(f, "event %s()\n", name.c_str()); fprintf(f, "\t{\n"); offset = 0; for ( auto& d : dvec ) { fprintf(f, "\t"); auto& val = d.GetVal(); bool define_local = d.IsFirstDef() && ! vtm.IsGlobal(val); if ( define_local ) fprintf(f, "local "); if ( d.NeedsLHS() ) { fprintf(f, "%s", vtm.ValName(val).c_str()); if ( define_local ) fprintf(f, ": %s", obj_desc_short(val->GetType().get()).c_str()); } auto anno = offset < num_pre ? " # from script" : ""; fprintf(f, "%s;%s\n", d.RHS().c_str(), anno); ++offset; } if ( ! dvec.empty() ) fprintf(f, "\n"); fprintf(f, "\tevent %s(%s);\n\n", ev->GetName().c_str(), args.c_str()); if ( successor.empty() ) { // The following isn't necessary with our current approach // to managing chains of events, which avoids having to set // exit_only_after_terminate=T. // fprintf(f, "\tterminate();\n"); } else { auto tm = vtm.TimeConstant(nt); fprintf(f, "\tset_network_time(%s);\n", tm.c_str()); fprintf(f, "\tevent __EventTrace::%s();\n", successor.c_str()); } fprintf(f, "\t}\n"); } void EventTrace::Generate(FILE* f, ValTraceMgr& vtm, const EventTrace* predecessor, std::string successor) const { if ( predecessor ) { auto& pre_deltas = predecessor->post_deltas; int num_pre = pre_deltas.size(); if ( num_pre > 0 ) { auto total_deltas = pre_deltas; total_deltas.insert(total_deltas.end(), deltas.begin(), deltas.end()); Generate(f, vtm, total_deltas, successor, num_pre); return; } } Generate(f, vtm, deltas, std::move(successor)); } void ValTraceMgr::TraceEventValues(std::shared_ptr et, const zeek::Args* args) { curr_ev = std::move(et); auto num_vals = vals.size(); std::string ev_args; for ( auto& a : *args ) { AddVal(a); if ( ! ev_args.empty() ) ev_args += ", "; ev_args += ValName(a); } curr_ev->SetArgs(std::move(ev_args)); // Now look for any values newly-processed with this event and // remember them so we can catch uses of them in future events. for ( auto i = num_vals; i < vals.size(); ++i ) { processed_vals.insert(vals[i].get()); ASSERT(val_names.count(vals[i].get()) > 0); } } void ValTraceMgr::FinishCurrentEvent(const zeek::Args* args) { auto num_vals = vals.size(); curr_ev->SetDoingPost(); for ( auto& a : *args ) AddVal(a); for ( auto i = num_vals; i < vals.size(); ++i ) processed_vals.insert(vals[i].get()); } const std::string& ValTraceMgr::ValName(const ValPtr& v) { auto find = val_names.find(v.get()); if ( find == val_names.end() ) find = val_names.insert({v.get(), GenValName(v)}).first; ValUsed(v); return find->second; } std::string ValTraceMgr::TimeConstant(double t) { if ( t < std::max(base_time, 1e6) ) return "double_to_time(" + std::to_string(t) + ")"; if ( ! base_time ) base_time = t; if ( t == base_time ) return "double_to_time(__base_time)"; t -= base_time; return "double_to_time(__base_time + " + std::to_string(t) + ")"; } void ValTraceMgr::AddVal(ValPtr v) { auto mapping = val_map.find(v.get()); if ( mapping == val_map.end() ) NewVal(v); else { auto vt = std::make_shared(v); AssessChange(vt.get(), mapping->second.get()); val_map[v.get()] = std::move(vt); } } void ValTraceMgr::NewVal(ValPtr v) { // Make sure the Val sticks around into the future. vals.push_back(v); auto vt = std::make_shared(v); AssessChange(vt.get(), nullptr); val_map[v.get()] = std::move(vt); } void ValTraceMgr::ValUsed(const ValPtr& v) { ASSERT(val_names.count(v.get()) > 0); if ( processed_vals.count(v.get()) > 0 ) // We saw this value when processing a previous event. globals.insert(v.get()); } void ValTraceMgr::AssessChange(const ValTrace* vt, const ValTrace* prev_vt) { DeltaVector deltas; vt->ComputeDelta(prev_vt, deltas); // Used to track deltas across the batch, to suppress redundant ones // (which can arise due to two aggregates both including the same // sub-element). std::unordered_set previous_deltas; for ( auto& d : deltas ) { auto vp = d->GetValTrace()->GetVal(); auto v = vp.get(); auto rhs = d->Generate(this); bool needs_lhs = d->NeedsLHS(); bool is_first_def = false; if ( needs_lhs && val_names.count(v) == 0 ) { TrackVar(v); is_first_def = true; } ASSERT(val_names.count(v) > 0); // The "/" in the following is just to have a delimiter // to make sure the string is unambiguous. auto full_delta = val_names[v] + "/" + rhs; if ( previous_deltas.count(full_delta) > 0 ) continue; previous_deltas.insert(std::move(full_delta)); ValUsed(vp); curr_ev->AddDelta(vp, std::move(rhs), needs_lhs, is_first_def); } auto& v = vt->GetVal(); if ( IsAggr(v->GetType()) && (prev_vt || ! IsUnspecifiedAggregate(v)) ) ValUsed(vt->GetVal()); } void ValTraceMgr::TrackVar(const Val* v) { std::string base_name = IsUnsupported(v) ? "UNSUPPORTED" : "val"; val_names[v] = "__" + base_name + std::to_string(num_vars++); } std::string ValTraceMgr::GenValName(const ValPtr& v) { if ( IsAggr(v->GetType()) && ! IsUnspecifiedAggregate(v) ) { // Aggregate shouldn't exist; create it ASSERT(val_map.count(v.get()) == 0); NewVal(v); return val_names[v.get()]; } // Non-aggregate (or unspecified aggregate) can be expressed using // a constant. auto t = v->GetType(); auto tag = t->Tag(); std::string rep; bool track_constant = false; switch ( tag ) { case TYPE_STRING: { auto s = v->AsStringVal(); rep = escape_string(s->Bytes(), s->Len()); track_constant = s->Len() > 0; break; } case TYPE_LIST: { auto lv = cast_intrusive(v); for ( auto& v_i : lv->Vals() ) { if ( ! rep.empty() ) rep += ", "; rep += ValName(v_i); } break; } case TYPE_FUNC: rep = v->AsFunc()->GetName(); break; case TYPE_TIME: { auto tm = v->AsDouble(); rep = TimeConstant(tm); if ( tm > 0.0 && rep.find("__base_time") == std::string::npos ) // We're not representing it using base_time. track_constant = true; break; } case TYPE_INTERVAL: rep = "double_to_interval(" + std::to_string(v->AsDouble()) + ")"; break; case TYPE_TABLE: rep = t->Yield() ? "table()" : "set()"; break; case TYPE_VECTOR: rep = "vector()"; break; case TYPE_PATTERN: case TYPE_PORT: case TYPE_ADDR: case TYPE_SUBNET: { ODesc d; v->Describe(&d); rep = d.Description(); track_constant = true; if ( tag == TYPE_ADDR || tag == TYPE_SUBNET ) { // Fix up deficiency that IPv6 addresses are // described without surrounding []'s. const auto& addr = tag == TYPE_ADDR ? v->AsAddr() : v->AsSubNet().Prefix(); if ( addr.GetFamily() == IPv6 ) rep = "[" + rep + "]"; } } break; default: { ODesc d; v->Describe(&d); rep = d.Description(); } } val_names[v.get()] = rep; vals.push_back(v); if ( track_constant ) constants[tag].insert(rep); std::array constants; return rep; } bool ValTraceMgr::IsUnspecifiedAggregate(const ValPtr& v) const { auto t = v->GetType()->Tag(); if ( t == TYPE_TABLE && v->GetType()->IsUnspecifiedTable() ) return true; if ( t == TYPE_VECTOR && v->GetType()->IsUnspecifiedVector() ) return true; return false; } bool ValTraceMgr::IsUnsupported(const Val* v) const { auto t = v->GetType()->Tag(); return t == TYPE_ANY || t == TYPE_FILE || t == TYPE_OPAQUE; } EventTraceMgr::EventTraceMgr(const std::string& trace_file) { f = fopen(trace_file.c_str(), "w"); if ( ! f ) reporter->FatalError("can't open event trace file %s: %s", trace_file.c_str(), strerror(errno)); } EventTraceMgr::~EventTraceMgr() { if ( f ) { if ( fclose(f) ) // Not fatal, won't do anything with it anymore anyhow. reporter->Error("failed to close event trace file: %s", strerror(errno)); f = nullptr; } } void EventTraceMgr::Generate() { if ( events.empty() ) return; fprintf(f, "module __EventTrace;\n\n"); auto bt = vtm.GetBaseTime(); if ( bt ) fprintf(f, "global __base_time = %.06f;\n\n", bt); for ( auto& e : events ) fprintf(f, "global %s: event();\n", e->GetName()); fprintf(f, "\nevent zeek_init() &priority=-999999\n"); fprintf(f, "\t{\n"); fprintf(f, "\tevent __EventTrace::%s();\n", events.front()->GetName()); fprintf(f, "\t}\n"); for ( auto i = 0U; i < events.size(); ++i ) { fprintf(f, "\n"); auto predecessor = i > 0 ? events[i - 1] : nullptr; auto successor = i + 1 < events.size() ? events[i + 1]->GetName() : ""; events[i]->Generate(f, vtm, predecessor.get(), successor); } const auto& constants = vtm.GetConstants(); for ( auto tag = 0; tag < NUM_TYPES; ++tag ) { auto& c_t = constants[tag]; if ( c_t.empty() && (tag != TYPE_TIME || ! bt) ) continue; fprintf(f, "\n# constants of type %s:\n", type_name(TypeTag(tag))); if ( tag == TYPE_TIME && bt ) fprintf(f, "#\t__base_time = %.06f\n", bt); for ( auto& c : c_t ) fprintf(f, "#\t%s\n", c.c_str()); } } void EventTraceMgr::StartEvent(const ScriptFunc* ev, const zeek::Args* args) { if ( script_events.count(ev->GetName()) > 0 ) return; auto nt = run_state::network_time; if ( nt == 0.0 || ev->GetName() == "zeek_init" ) return; if ( ! vtm.GetBaseTime() ) vtm.SetBaseTime(nt); auto et = std::make_shared(ev, nt, events.size()); events.emplace_back(et); vtm.TraceEventValues(std::move(et), args); } void EventTraceMgr::EndEvent(const ScriptFunc* ev, const zeek::Args* args) { if ( script_events.count(ev->GetName()) > 0 ) return; if ( run_state::network_time > 0.0 && ev->GetName() != "zeek_init" ) vtm.FinishCurrentEvent(args); } void EventTraceMgr::ScriptEventQueued(const EventHandlerPtr& h) { script_events.insert(h->Name()); } } // namespace zeek::detail