From 8dd3debeae2bbd38b8e9e206ea2d7709ba9ec375 Mon Sep 17 00:00:00 2001 From: Robin Sommer Date: Thu, 2 May 2024 08:19:26 +0200 Subject: [PATCH] Spicy: Prepare for supporting forwarding to protocols other than TCP. So far the Spicy runtime supported forwarding data into other analyzers only for TCP analyzers. This puts branching logic in place that let the relevant runtime functions dispatch differently based on the target transport-layer protocol. We don't implement anything else than TCP yet; that will come next. Along with the internal changes, this also updates the user-visible runtime function to pass protocol information in. For now, this likewise remains limited to TCP. The function signatures are chosen so that they stay backwards-compatible to previous Spicy version. In particular, they default to TCP where not otherwise specified. --- scripts/spicy/zeek.spicy | 70 ++++++-- src/spicy/runtime-support.cc | 307 +++++++++++++++++++++++------------ src/spicy/runtime-support.h | 38 ++++- 3 files changed, 286 insertions(+), 129 deletions(-) diff --git a/scripts/spicy/zeek.spicy b/scripts/spicy/zeek.spicy index 5f93b74480..a6f3b1f344 100644 --- a/scripts/spicy/zeek.spicy +++ b/scripts/spicy/zeek.spicy @@ -2,6 +2,8 @@ module zeek; +import spicy; + # Note: Retain the formatting here, doc/scripts/autogen-spicy-lib is picking up on that. %cxx-include = "zeek/spicy/runtime-support.h"; @@ -48,46 +50,82 @@ public type ProtocolHandle = __library_type("zeek::spicy::rt::ProtocolHandle"); ## Adds a Zeek-side child protocol analyzer to the current connection. ## -## If the same analyzer was added previously with protocol_handle_get_or_create or -## protocol_begin with same argument, and not closed with protocol_handle_close -## or protocol_end, no new analyzer will be added. +## If the same analyzer was added previously with `protocol_handle_get_or_create` or +## `protocol_begin` with same argument, and not closed with `protocol_handle_close` +## or `protocol_end`, no new analyzer will be added. ## ## See `protocol_handle_get_or_create` for the error semantics of this function. ## ## analyzer: type of analyzer to instantiate, specified through its Zeek-side -## name (similar to what Zeek's signature action `enable` takes); if not -## specified, Zeek will perform its usual dynamic protocol detection to figure -## out how to parse the data (the latter will work only for TCP protocols, though.) -public function protocol_begin(analyzer: optional = Null) : void &cxxname="zeek::spicy::rt::protocol_begin"; +## name (similar to what Zeek's signature action `enable` takes) +## +## protocol: the transport-layer protocol that the analyzer uses; only TCP is +## currently supported here +## +## Note: For backwards compatibility, the analyzer argument can be left unset to add +## a DPD analyzer. This use is deprecated, though; use the single-argument version of +## `protocol_begin` for that instead. +public function protocol_begin(analyzer: optional, protocol: spicy::Protocol = spicy::Protocol::TCP) : void &cxxname="zeek::spicy::rt::protocol_begin"; + +## Adds a Zeek-side DPD child protocol analyzer performing dynamic protocol detection +## on subsequently provided data. +## +## If the same DPD analyzer was added previously with `protocol_handle_get_or_create` or +## `protocol_begin` with same argument, and not closed with `protocol_handle_close` +## or `protocol_end`, no new analyzer will be added. +## +## See `protocol_handle_get_or_create` for the error semantics of this function. +## +## protocol: the transport-layer protocol on which to perform protocol detection; +## only TCP is currently supported here +public function protocol_begin(protocol: spicy::Protocol = spicy::Protocol::TCP) : void &cxxname="zeek::spicy::rt::protocol_begin"; ## Gets a handle to a Zeek-side child protocol analyzer for the current connection. ## -## If no such child exists it will be added; otherwise a handle to the +## If no such child exists yet it will be added; otherwise a handle to the ## existing child protocol analyzer will be returned. ## -## This function will return an error +## This function will return an error if: ## -## - if not called from a protocol analyzer, or -## - the requested child protocol analyzer is unknown, or +## - not called from a protocol analyzer, or +## - the requested child protocol analyzer is of unknown type or not support by the requested transport protocol, or ## - creation of a child analyzer of the requested type was prevented by a ## previous call of `disable_analyzer` with `prevent=T` ## -## analyzer: type of analyzer to instantiate, specified through its Zeek-side +## analyzer: type of analyzer to get or instantiate, specified through its Zeek-side ## name (similar to what Zeek's signature action `enable` takes). -public function protocol_handle_get_or_create(analyzer: string) : ProtocolHandle &cxxname="zeek::spicy::rt::protocol_handle_get_or_create"; +## +## protocol: the transport-layer protocol that the analyser uses; only TCP is +## currently supported here +## +public function protocol_handle_get_or_create(analyzer: string, protocol: spicy::Protocol = spicy::Protocol::TCP) : ProtocolHandle &cxxname="zeek::spicy::rt::protocol_handle_get_or_create"; -## Forwards protocol data to all previously instantiated Zeek-side child protocol analyzers. +## Forwards protocol data to all previously instantiated Zeek-side child protocol analyzers of a given transport-layer. ## ## is_orig: true to feed the data to the child's originator side, false for the responder +## ## data: chunk of data to forward to child analyzer -## h: optional handle to the child analyzer to forward data into, else forward to all child analyzers -public function protocol_data_in(is_orig: bool, data: bytes, h: optional = Null) : void &cxxname="zeek::spicy::rt::protocol_data_in"; +## +## protocol: the transport-layer protocol of the children to forward to; only TCP is currently supported here +public function protocol_data_in(is_orig: bool, data: bytes, protocol: spicy::Protocol = spicy::Protocol::TCP) : void &cxxname="zeek::spicy::rt::protocol_data_in"; + +## Forwards protocol data to a specific previously instantiated Zeek-side child analyzer. +## +## is_orig: true to feed the data to the child's originator side, false for the responder +## +## data: chunk of data to forward to child analyzer +## +## h: handle to the child analyzer to forward data into +public function protocol_data_in(is_orig: bool, data: bytes, h: ProtocolHandle) : void &cxxname="zeek::spicy::rt::protocol_data_in"; ## Signals a gap in input data to all previously instantiated Zeek-side child protocol analyzers. ## ## is_orig: true to signal gap to the child's originator side, false for the responder +## ## offset: start offset of gap in input stream +## ## len: size of gap +## ## h: optional handle to the child analyzer signal a gap to, else signal to all child analyzers public function protocol_gap(is_orig: bool, offset: uint64, len: uint64, h: optional = Null) : void &cxxname="zeek::spicy::rt::protocol_gap"; diff --git a/src/spicy/runtime-support.cc b/src/spicy/runtime-support.cc index e6b340e0be..a95d3a9947 100644 --- a/src/spicy/runtime-support.cc +++ b/src/spicy/runtime-support.cc @@ -485,15 +485,16 @@ void rt::weird(const std::string& id, const std::string& addl) { throw ValueUnavailable("none of $conn, $file, or $packet available for weird reporting"); } -void rt::protocol_begin(const std::optional& analyzer) { +void rt::protocol_begin(const std::optional& analyzer, const ::hilti::rt::Protocol& proto) { auto _ = hilti::rt::profiler::start("zeek/rt/protocol_begin"); if ( analyzer ) { - protocol_handle_get_or_create(*analyzer); + protocol_handle_get_or_create(*analyzer, proto); return; } // Instantiate a DPD analyzer. + auto cookie = static_cast(hilti::rt::context::cookie()); assert(cookie); @@ -501,36 +502,46 @@ void rt::protocol_begin(const std::optional& analyzer) { if ( ! c ) throw ValueUnavailable("no current connection available"); - // Use a Zeek PIA stream analyzer performing DPD. - auto pia_tcp = std::make_unique(c->analyzer->Conn()); + switch ( proto.value() ) { + case ::hilti::rt::Protocol::TCP: { + auto pia_tcp = std::make_unique(c->analyzer->Conn()); + pia_tcp->FirstPacket(true, nullptr); + pia_tcp->FirstPacket(false, nullptr); - // Forward empty payload to trigger lifecycle management in this analyzer tree. - c->analyzer->ForwardStream(0, reinterpret_cast(c->analyzer), true); - c->analyzer->ForwardStream(0, reinterpret_cast(c->analyzer), false); + // Forward empty payload to trigger lifecycle management in this analyzer tree. + c->analyzer->ForwardStream(0, reinterpret_cast(c->analyzer), true); + c->analyzer->ForwardStream(0, reinterpret_cast(c->analyzer), false); - // Direct child of this type already exists. We ignore this silently - // because that makes usage nicer if either side of the connection - // might end up creating the analyzer; this way the user doesn't - // need to track what the other side already did. - // - // We inspect the children directly to work around zeek/zeek#2899. - const auto& children = c->analyzer->GetChildren(); - if ( auto it = std::find_if(children.begin(), children.end(), - [&](const auto& it) { - return ! it->Removing() && ! it->IsFinished() && - it->GetAnalyzerTag() == pia_tcp->GetAnalyzerTag(); - }); - it != children.end() ) - return; + // If the child already exists, do not add it again so this function is idempotent. + // + // We inspect the children directly to work around zeek/zeek#2899. + const auto& children = c->analyzer->GetChildren(); + if ( auto it = std::find_if(children.begin(), children.end(), + [&](const auto& it) { + return ! it->Removing() && ! it->IsFinished() && + it->GetAnalyzerName() == pia_tcp->GetAnalyzerTag(); + }); + it != children.end() ) + return; - auto child = pia_tcp.release(); - c->analyzer->AddChildAnalyzer(child); + auto child = pia_tcp.release(); + c->analyzer->AddChildAnalyzer(child); + break; + } - child->FirstPacket(true, nullptr); - child->FirstPacket(false, nullptr); + case ::hilti::rt::Protocol::UDP: throw Unsupported("protocol_begin: UDPnot supported for DPD"); + + case ::hilti::rt::Protocol::ICMP: throw Unsupported("protocol_begin: ICMP not supported for DPD"); + + case ::hilti::rt::Protocol::Undef: throw InvalidValue("protocol_begin: no protocol specified for DPD"); + + default: throw InvalidValue("protocol_begin: unknown protocol for DPD"); + } } -rt::ProtocolHandle rt::protocol_handle_get_or_create(const std::string& analyzer) { +void rt::protocol_begin(const ::hilti::rt::Protocol& proto) { return protocol_begin(std::nullopt, proto); } + +rt::ProtocolHandle rt::protocol_handle_get_or_create(const std::string& analyzer, const ::hilti::rt::Protocol& proto) { auto _ = hilti::rt::profiler::start("zeek/rt/protocol_handle_get_or_create"); auto cookie = static_cast(hilti::rt::context::cookie()); assert(cookie); @@ -539,56 +550,73 @@ rt::ProtocolHandle rt::protocol_handle_get_or_create(const std::string& analyzer if ( ! c ) throw ValueUnavailable("no current connection available"); - // Forward empty payload to trigger lifecycle management in this analyzer tree. - c->analyzer->ForwardStream(0, reinterpret_cast(c->analyzer), true); - c->analyzer->ForwardStream(0, reinterpret_cast(c->analyzer), false); + switch ( proto.value() ) { + case ::hilti::rt::Protocol::TCP: { + // Forward empty payload to trigger lifecycle management in this analyzer tree. + c->analyzer->ForwardStream(0, reinterpret_cast(c->analyzer), true); + c->analyzer->ForwardStream(0, reinterpret_cast(c->analyzer), false); - // If the child already exists, do not add it again so this function is idempotent. - // - // We inspect the children directly to work around zeek/zeek#2899. - const auto& children = c->analyzer->GetChildren(); - if ( auto it = std::find_if(children.begin(), children.end(), - [&](const auto& it) { - return ! it->Removing() && ! it->IsFinished() && it->GetAnalyzerName() == analyzer; - }); - it != children.end() ) - return rt::ProtocolHandle((*it)->GetID()); + // If the child already exists, do not add it again so this function is idempotent. + // + // We inspect the children directly to work around zeek/zeek#2899. + const auto& children = c->analyzer->GetChildren(); + if ( auto it = std::find_if(children.begin(), children.end(), + [&](const auto& it) { + return ! it->Removing() && ! it->IsFinished() && + it->GetAnalyzerName() == analyzer; + }); + it != children.end() ) + return rt::ProtocolHandle((*it)->GetID(), proto); - auto child = analyzer_mgr->InstantiateAnalyzer(analyzer.c_str(), c->analyzer->Conn()); - if ( ! child ) - throw ZeekError(::hilti::rt::fmt("unknown analyzer '%s' requested", analyzer)); + auto child = analyzer_mgr->InstantiateAnalyzer(analyzer.c_str(), c->analyzer->Conn()); + if ( ! child ) + throw ZeekError(::hilti::rt::fmt("unknown analyzer '%s' requested", analyzer)); - // If we had no such child before but cannot add it the analyzer was prevented. - // - // NOTE: We make this a hard error since returning e.g., an empty optional - // here would make it easy to incorrectly use the return value with e.g., - // `protocol_data_in` or `protocol_gap`. - if ( ! c->analyzer->AddChildAnalyzer(child) ) - throw ZeekError(::hilti::rt::fmt("creation of child analyzer %s was prevented", analyzer)); + // If we had no such child before but cannot add it the analyzer was prevented. + // + // NOTE: We make this a hard error since returning e.g., an empty optional + // here would make it easy to incorrectly use the return value with e.g., + // `protocol_data_in` or `protocol_gap`. + if ( ! c->analyzer->AddChildAnalyzer(child) ) + throw ZeekError(::hilti::rt::fmt("creation of child analyzer %s was prevented", analyzer)); - if ( c->analyzer->Conn()->ConnTransport() != TRANSPORT_TCP ) { - // Some TCP application analyzer may expect to have access to a TCP - // analyzer. To make that work, we'll create a fake TCP analyzer, - // just so that they have something to access. It won't - // semantically have any "TCP" to analyze obviously. - c->fake_tcp = std::make_shared(c->analyzer->Conn()); - static_cast(c->fake_tcp.get()) - ->Done(); // will never see packets; cast to get around protected inheritance + if ( c->analyzer->Conn()->ConnTransport() != TRANSPORT_TCP ) { + // Some TCP application analyzer may expect to have access to a TCP + // analyzer. To make that work, we'll create a fake TCP analyzer, + // just so that they have something to access. It won't + // semantically have any "TCP" to analyze obviously. + c->fake_tcp = std::make_shared(c->analyzer->Conn()); + static_cast(c->fake_tcp.get()) + ->Done(); // will never see packets; cast to get around protected inheritance + } + + auto* child_as_tcp = dynamic_cast(child); + if ( ! child_as_tcp ) + throw ZeekError( + ::hilti::rt::fmt("could not add analyzer '%s' to connection; not a TCP-based analyzer", analyzer)); + + if ( c->fake_tcp ) + child_as_tcp->SetTCP(c->fake_tcp.get()); + + return rt::ProtocolHandle(child->GetID(), proto); + } + + case ::hilti::rt::Protocol::UDP: { + throw Unsupported("protocol_handle_get_or_create: UDP not supported"); + } + + case ::hilti::rt::Protocol::ICMP: throw Unsupported("protocol_handle_get_or_create: ICMP not supported"); + + case ::hilti::rt::Protocol::Undef: throw InvalidValue("protocol_handle_get_or_create: no protocol specified"); + + default: throw InvalidValue("protocol_handle_get_or_create: unknown protocol"); } - - auto* child_as_tcp = dynamic_cast(child); - if ( ! child_as_tcp ) - throw ZeekError( - ::hilti::rt::fmt("could not add analyzer '%s' to connection; not a TCP-based analyzer", analyzer)); - - if ( c->fake_tcp ) - child_as_tcp->SetTCP(c->fake_tcp.get()); - - return rt::ProtocolHandle(child->GetID()); } -void rt::protocol_data_in(const hilti::rt::Bool& is_orig, const hilti::rt::Bytes& data, - const std::optional& h) { +namespace zeek::spicy::rt { +static void protocol_data_in(const hilti::rt::Bool& is_orig, const hilti::rt::Bytes& data, + const std::optional<::hilti::rt::Protocol>& proto, + const std::optional& h) { auto _ = hilti::rt::profiler::start("zeek/rt/protocol_data_in"); auto cookie = static_cast(hilti::rt::context::cookie()); assert(cookie); @@ -597,25 +625,65 @@ void rt::protocol_data_in(const hilti::rt::Bool& is_orig, const hilti::rt::Bytes if ( ! c ) throw ValueUnavailable("no current connection available"); - auto len = data.size(); - auto* data_ = reinterpret_cast(data.data()); + ::hilti::rt::Protocol protocol_to_use = ::hilti::rt::Protocol::Undef; - if ( h ) { - if ( auto* output_handler = c->analyzer->GetOutputHandler() ) - output_handler->DeliverStream(len, data_, is_orig); + if ( proto ) { + if ( h && h->protocol() != *proto ) + throw InvalidValue("protocol_data_in: protocol mismatches with analyzer handle"); - auto* child = c->analyzer->FindChild(h->id()); - if ( ! child ) - throw ValueUnavailable(hilti::rt::fmt("unknown child analyzer %s", *h)); - - if ( child->IsFinished() || child->Removing() ) - throw ValueUnavailable(hilti::rt::fmt("child analyzer %s no longer exist", *h)); - - child->NextStream(len, data_, is_orig); + protocol_to_use = *proto; } + else if ( h ) + protocol_to_use = h->protocol(); - else - c->analyzer->ForwardStream(len, data_, is_orig); + if ( protocol_to_use == ::hilti::rt::Protocol::Undef ) + throw InvalidValue("protocol_data_in: cannot determine protocol to use"); + + switch ( protocol_to_use.value() ) { + case ::hilti::rt::Protocol::TCP: { + auto len = data.size(); + auto* data_ = reinterpret_cast(data.data()); + + if ( h ) { + if ( auto* output_handler = c->analyzer->GetOutputHandler() ) + output_handler->DeliverStream(len, data_, is_orig); + + auto* child = c->analyzer->FindChild(h->id()); + if ( ! child ) + throw ValueUnavailable(hilti::rt::fmt("unknown child analyzer %s", *h)); + + if ( child->IsFinished() || child->Removing() ) + throw ValueUnavailable(hilti::rt::fmt("child analyzer %s no longer exist", *h)); + + child->NextStream(len, data_, is_orig); + } + + else + c->analyzer->ForwardStream(len, data_, is_orig); + + break; + } + + case ::hilti::rt::Protocol::UDP: { + throw Unsupported("protocol_data_in: UDP not supported"); + } + + case ::hilti::rt::Protocol::ICMP: throw Unsupported("protocol_data_in: ICMP not supported"); + + case ::hilti::rt::Protocol::Undef: hilti::rt::cannot_be_reached(); + + default: throw InvalidValue("protocol_data_in: unknown protocol"); + } +} +} // namespace zeek::spicy::rt + +void rt::protocol_data_in(const hilti::rt::Bool& is_orig, const hilti::rt::Bytes& data, + const ::hilti::rt::Protocol& proto) { + protocol_data_in(is_orig, data, proto, {}); +} + +void rt::protocol_data_in(const hilti::rt::Bool& is_orig, const hilti::rt::Bytes& data, const rt::ProtocolHandle& h) { + protocol_data_in(is_orig, data, {}, h); } void rt::protocol_gap(const hilti::rt::Bool& is_orig, const hilti::rt::integer::safe& offset, @@ -628,22 +696,38 @@ void rt::protocol_gap(const hilti::rt::Bool& is_orig, const hilti::rt::integer:: if ( ! c ) throw ValueUnavailable("no current connection available"); - if ( h ) { - if ( auto* output_handler = c->analyzer->GetOutputHandler() ) - output_handler->Undelivered(offset, len, is_orig); + switch ( h->protocol().value() ) { + case ::hilti::rt::Protocol::TCP: { + if ( h ) { + if ( auto* output_handler = c->analyzer->GetOutputHandler() ) + output_handler->Undelivered(offset, len, is_orig); - auto* child = c->analyzer->FindChild(h->id()); - if ( ! child ) - throw ValueUnavailable(hilti::rt::fmt("unknown child analyzer %s", *h)); + auto* child = c->analyzer->FindChild(h->id()); + if ( ! child ) + throw ValueUnavailable(hilti::rt::fmt("unknown child analyzer %s", *h)); - if ( child->IsFinished() || child->Removing() ) - throw ValueUnavailable(hilti::rt::fmt("child analyzer %s no longer exist", *h)); + if ( child->IsFinished() || child->Removing() ) + throw ValueUnavailable(hilti::rt::fmt("child analyzer %s no longer exist", *h)); - child->NextUndelivered(offset, len, is_orig); + child->NextUndelivered(offset, len, is_orig); + } + + else + c->analyzer->ForwardUndelivered(offset, len, is_orig); + + break; + } + + case ::hilti::rt::Protocol::UDP: { + throw Unsupported("protocol_gap: UDP not supported"); + } + + case ::hilti::rt::Protocol::ICMP: throw Unsupported("protocol_gap: ICMP not supported"); + + case ::hilti::rt::Protocol::Undef: throw InvalidValue("protocol_gap: no protocol specified"); + + default: throw InvalidValue("protocol_gap: unknown protocol"); } - - else - c->analyzer->ForwardUndelivered(offset, len, is_orig); } void rt::protocol_end() { @@ -668,17 +752,32 @@ void rt::protocol_handle_close(const ProtocolHandle& handle) { if ( ! c ) throw ValueUnavailable("no current connection available"); - auto child = c->analyzer->FindChild(handle.id()); - if ( ! child ) - throw ValueUnavailable(hilti::rt::fmt("unknown child analyzer %s", handle)); + switch ( handle.protocol().value() ) { + case ::hilti::rt::Protocol::TCP: { + auto child = c->analyzer->FindChild(handle.id()); + if ( ! child ) + throw ValueUnavailable(hilti::rt::fmt("unknown child analyzer %s", handle)); - if ( child->IsFinished() || child->Removing() ) - throw ValueUnavailable(hilti::rt::fmt("child analyzer %s no longer exist", handle)); + if ( child->IsFinished() || child->Removing() ) + throw ValueUnavailable(hilti::rt::fmt("child analyzer %s no longer exist", handle)); - child->NextEndOfData(true); - child->NextEndOfData(false); + child->NextEndOfData(true); + child->NextEndOfData(false); - c->analyzer->RemoveChildAnalyzer(handle.id()); + c->analyzer->RemoveChildAnalyzer(handle.id()); + break; + } + + case ::hilti::rt::Protocol::UDP: { + throw Unsupported("protocol_handle_close: UDP not supported"); + } + + case ::hilti::rt::Protocol::ICMP: throw Unsupported("protocol_handle_close: ICMP not supported"); + + case ::hilti::rt::Protocol::Undef: throw InvalidValue("protocol_handle_close: no protocol specified"); + + default: throw InvalidValue("protocol_handle_close: unknown protocol"); + } } rt::cookie::FileState* rt::cookie::FileStateStack::push(std::optional fid_provided) { diff --git a/src/spicy/runtime-support.h b/src/spicy/runtime-support.h index 95d08f219f..9b2a5d448e 100644 --- a/src/spicy/runtime-support.h +++ b/src/spicy/runtime-support.h @@ -294,7 +294,7 @@ void reject_protocol(const std::string& reason = "protocol rejected"); class ProtocolHandle { public: ProtocolHandle() {} - explicit ProtocolHandle(uint64_t id) : _id(id) {} + explicit ProtocolHandle(uint64_t id, ::hilti::rt::Protocol proto) : _id(id), _proto(proto) {} uint64_t id() const { if ( ! _id ) @@ -303,6 +303,8 @@ public: 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)"; @@ -316,38 +318,56 @@ public: 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 if given, the Zeek-side name of the analyzer to instantiate; - * if not given, DPD will be used + * @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); +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. */ -ProtocolHandle protocol_handle_get_or_create(const std::string& analyzer); +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. + * 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 the child analyzer to stream data into + * @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 std::optional& h = {}); +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