text: %extern{
  #include <cstdlib>
  #include <vector>
  #include <string>
  
  #include "zeek/net_util.h"
  #include "zeek/util.h"
  %}
  
  %header{
  zeek::AddrValPtr network_address_to_val(const ASN1Encoding* na);
  zeek::AddrValPtr network_address_to_val(const NetworkAddress* na);
  zeek::ValPtr     asn1_obj_to_val(const ASN1Encoding* obj);
  
  zeek::RecordValPtr build_hdr(const Header* header);
  zeek::RecordValPtr build_hdrV3(const Header* header);
  zeek::VectorValPtr build_bindings(const VarBindList* vbl);
  zeek::RecordValPtr build_pdu(const CommonPDU* pdu);
  zeek::RecordValPtr build_trap_pdu(const TrapPDU* pdu);
  zeek::RecordValPtr build_bulk_pdu(const GetBulkRequestPDU* pdu);
  %}
  
  %code{
  
  zeek::AddrValPtr network_address_to_val(const NetworkAddress* na)
  	{
  	return network_address_to_val(na->encoding());
  	}
  
  zeek::AddrValPtr network_address_to_val(const ASN1Encoding* na)
  	{
  	bytestring const& bs = na->content();
  
  	// IPv6 can probably be presumed to be a octet string of length 16,
  	// but standards don't seem to currently make any provisions for IPv6,
  	// so ignore anything that can't be IPv4.
  	if ( bs.length() != 4 )
  		return zeek::make_intrusive<zeek::AddrVal>(zeek::IPAddr());
  
  	const u_char* data = reinterpret_cast<const u_char*>(bs.data());
  	uint32 network_order = zeek::extract_uint32(data);
  	return zeek::make_intrusive<zeek::AddrVal>(ntohl(network_order));
  	}
  
  zeek::ValPtr asn1_obj_to_val(const ASN1Encoding* obj)
  	{
  	zeek::RecordValPtr rval = zeek::make_intrusive<zeek::RecordVal>(zeek::BifType::Record::SNMP::ObjectValue);
  	uint8 tag = obj->meta()->tag();
  
  	rval->Assign(0, tag);
  
  	switch ( tag ) {
  	case VARBIND_UNSPECIFIED_TAG:
  	case VARBIND_NOSUCHOBJECT_TAG:
  	case VARBIND_NOSUCHINSTANCE_TAG:
  	case VARBIND_ENDOFMIBVIEW_TAG:
  		break;
  
  	case ASN1_OBJECT_IDENTIFIER_TAG:
  		rval->Assign(1, asn1_oid_to_val(obj));
  		break;
  
  	case ASN1_INTEGER_TAG:
  		rval->Assign(2, asn1_integer_to_val(obj, zeek::TYPE_INT));
  		break;
  
  	case APP_COUNTER32_TAG:
  	case APP_UNSIGNED32_TAG:
  	case APP_TIMETICKS_TAG:
  	case APP_COUNTER64_TAG:
  		rval->Assign(3, asn1_integer_to_val(obj, zeek::TYPE_COUNT));
  		break;
  
  	case APP_IPADDRESS_TAG:
  		rval->Assign(4, network_address_to_val(obj));
  		break;
  
  	case ASN1_OCTET_STRING_TAG:
  	case APP_OPAQUE_TAG:
  	default:
  		rval->Assign(5, asn1_octet_string_to_val(obj));
  		break;
  	}
  
  	return rval;
  	}
  
  zeek::ValPtr time_ticks_to_val(const TimeTicks* tt)
  	{
  	return asn1_integer_to_val(tt->asn1_integer(), zeek::TYPE_COUNT);
  	}
  
  zeek::RecordValPtr build_hdr(const Header* header)
  	{
  	auto rv = zeek::make_intrusive<zeek::RecordVal>(zeek::BifType::Record::SNMP::Header);
  	rv->Assign(0, static_cast<uint64_t>(header->version()));
  
  	switch ( header->version() ) {
  	case SNMPV1_TAG:
  		{
  		auto v1 = zeek::make_intrusive<zeek::RecordVal>(zeek::BifType::Record::SNMP::HeaderV1);
  		v1->Assign(0, asn1_octet_string_to_val(header->v1()->community()));
  		rv->Assign(1, std::move(v1));
  		}
  		break;
  
  	case SNMPV2_TAG:
  		{
  		auto v2 = zeek::make_intrusive<zeek::RecordVal>(zeek::BifType::Record::SNMP::HeaderV2);
  		v2->Assign(0, asn1_octet_string_to_val(header->v2()->community()));
  		rv->Assign(2, std::move(v2));
  		}
  		break;
  
  	case SNMPV3_TAG:
  		{
  		rv->Assign(3, build_hdrV3(header));
  		}
  		break;
  	}
  
  	return rv;
  	}
  
  zeek::RecordValPtr build_hdrV3(const Header* header)
  	{
  	auto v3 = zeek::make_intrusive<zeek::RecordVal>(zeek::BifType::Record::SNMP::HeaderV3);
  	const v3Header* v3hdr = header->v3();
  	const v3HeaderData* global_data = v3hdr->global_data();
  	bytestring const& flags = global_data->flags()->encoding()->content();
  	uint8 flags_byte = flags.length() > 0 ? flags[0] : 0;
  
  	v3->Assign(0, asn1_integer_to_val(global_data->id(), zeek::TYPE_COUNT));
  	v3->Assign(1, asn1_integer_to_val(global_data->max_size(), zeek::TYPE_COUNT));
  	v3->Assign(2, flags_byte);
  	v3->Assign(3, static_cast<bool>(flags_byte & 0x01));
  	v3->Assign(4, static_cast<bool>(flags_byte & 0x02));
  	v3->Assign(5, static_cast<bool>(flags_byte & 0x04));
  	v3->Assign(6, asn1_integer_to_val(global_data->security_model(), zeek::TYPE_COUNT));
  	v3->Assign(7, asn1_octet_string_to_val(v3hdr->security_parameters()));
  
  	if ( v3hdr->next()->tag() == ASN1_SEQUENCE_TAG )
  		{
  		const v3ScopedPDU* spdu = v3hdr->plaintext_pdu();
  		auto rv = zeek::make_intrusive<zeek::RecordVal>(zeek::BifType::Record::SNMP::ScopedPDU_Context);
  		rv->Assign(0, asn1_octet_string_to_val(spdu->context_engine_id()));
  		rv->Assign(1, asn1_octet_string_to_val(spdu->context_name()));
  		v3->Assign(8, std::move(rv));
  		}
  
  	return v3;
  	}
  
  zeek::VectorValPtr build_bindings(const VarBindList* vbl)
  	{
  	auto vv = zeek::make_intrusive<zeek::VectorVal>(zeek::BifType::Vector::SNMP::Bindings);
  
  	for ( size_t i = 0; i < vbl->bindings()->size(); ++i )
  		{
  		VarBind* vb = (*vbl->bindings())[i];
  		auto binding = zeek::make_intrusive<zeek::RecordVal>(zeek::BifType::Record::SNMP::Binding);
  		binding->Assign(0, asn1_oid_to_val(vb->name()->oid()));
  		binding->Assign(1, asn1_obj_to_val(vb->value()->encoding()));
  		vv->Assign(i, std::move(binding));
  		}
  
  	return vv;
  	}
  
  zeek::RecordValPtr build_pdu(const CommonPDU* pdu)
  	{
  	auto rv = zeek::make_intrusive<zeek::RecordVal>(zeek::BifType::Record::SNMP::PDU);
  	rv->Assign(0, asn1_integer_to_val(pdu->request_id(), zeek::TYPE_INT));
  	rv->Assign(1, asn1_integer_to_val(pdu->error_status(), zeek::TYPE_INT));
  	rv->Assign(2, asn1_integer_to_val(pdu->error_index(), zeek::TYPE_INT));
  	rv->Assign(3, build_bindings(pdu->var_bindings()));
  	return rv;
  	}
  
  zeek::RecordValPtr build_trap_pdu(const TrapPDU* pdu)
  	{
  	auto rv = zeek::make_intrusive<zeek::RecordVal>(zeek::BifType::Record::SNMP::TrapPDU);
  	rv->Assign(0, asn1_oid_to_val(pdu->enterprise()));
  	rv->Assign(1, network_address_to_val(pdu->agent_addr()));
  	rv->Assign(2, asn1_integer_to_val(pdu->generic_trap(), zeek::TYPE_INT));
  	rv->Assign(3, asn1_integer_to_val(pdu->specific_trap(), zeek::TYPE_INT));
  	rv->Assign(4, time_ticks_to_val(pdu->time_stamp()));
  	rv->Assign(5, build_bindings(pdu->var_bindings()));
  	return rv;
  	}
  
  zeek::RecordValPtr build_bulk_pdu(const GetBulkRequestPDU* pdu)
  	{
  	auto rv = zeek::make_intrusive<zeek::RecordVal>(zeek::BifType::Record::SNMP::BulkPDU);
  	rv->Assign(0, asn1_integer_to_val(pdu->request_id(), zeek::TYPE_INT));
  	rv->Assign(1, asn1_integer_to_val(pdu->non_repeaters(), zeek::TYPE_COUNT));
  	rv->Assign(2, asn1_integer_to_val(pdu->max_repititions(), zeek::TYPE_COUNT));
  	rv->Assign(3, build_bindings(pdu->var_bindings()));
  	return rv;
  	}
  %}
  
  refine connection SNMP_Conn += {
  
  	function proc_get_request(pdu: GetRequestPDU): bool
  		%{
  		if ( ! snmp_get_request )
  			return false;
  
  		zeek::BifEvent::enqueue_snmp_get_request(zeek_analyzer(),
  		                                   zeek_analyzer()->Conn(),
  		                                   ${pdu.header.is_orig},
  		                                   build_hdr(${pdu.header}),
  		                                   build_pdu(${pdu.pdu}));
  		return true;
  		%}
  
  	function proc_get_next_request(pdu: GetNextRequestPDU): bool
  		%{
  		if ( ! snmp_get_next_request )
  			return false;
  
  		zeek::BifEvent::enqueue_snmp_get_next_request(zeek_analyzer(),
  		                                        zeek_analyzer()->Conn(),
  		                                        ${pdu.header.is_orig},
  		                                        build_hdr(${pdu.header}),
  		                                        build_pdu(${pdu.pdu}));
  		return true;
  		%}
  
  	function proc_response(pdu: ResponsePDU): bool
  		%{
  		if ( ! snmp_response )
  			return false;
  
  		zeek::BifEvent::enqueue_snmp_response(zeek_analyzer(),
  		                                zeek_analyzer()->Conn(),
  		                                ${pdu.header.is_orig},
  		                                build_hdr(${pdu.header}),
  		                                build_pdu(${pdu.pdu}));
  		return true;
  		%}
  
  	function proc_set_request(pdu: SetRequestPDU): bool
  		%{
  		if ( ! snmp_set_request )
  			return false;
  
  		zeek::BifEvent::enqueue_snmp_set_request(zeek_analyzer(),
  		                                   zeek_analyzer()->Conn(),
  		                                   ${pdu.header.is_orig},
  		                                   build_hdr(${pdu.header}),
  		                                   build_pdu(${pdu.pdu}));
  		return true;
  		%}
  
  	function proc_trap(pdu: TrapPDU): bool
  		%{
  		if ( ! snmp_trap )
  			return false;
  
  		zeek::BifEvent::enqueue_snmp_trap(zeek_analyzer(),
  		                            zeek_analyzer()->Conn(),
  		                            ${pdu.header.is_orig},
  		                            build_hdr(${pdu.header}),
  		                            build_trap_pdu(${pdu}));
  		return true;
  		%}
  
  	function proc_get_bulk_request(pdu: GetBulkRequestPDU): bool
  		%{
  		if ( ! snmp_get_bulk_request )
  			return false;
  
  		zeek::BifEvent::enqueue_snmp_get_bulk_request(zeek_analyzer(),
  		                                        zeek_analyzer()->Conn(),
  		                                        ${pdu.header.is_orig},
  		                                        build_hdr(${pdu.header}),
  		                                        build_bulk_pdu(${pdu}));
  		return true;
  		%}
  
  	function proc_inform_request(pdu: InformRequestPDU): bool
  		%{
  		if ( ! snmp_inform_request )
  			return false;
  
  		zeek::BifEvent::enqueue_snmp_inform_request(zeek_analyzer(),
  		                                      zeek_analyzer()->Conn(),
  		                                      ${pdu.header.is_orig},
  		                                      build_hdr(${pdu.header}),
  		                                      build_pdu(${pdu.pdu}));
  		return true;
  		%}
  
  	function proc_v2_trap(pdu: v2TrapPDU): bool
  		%{
  		if ( ! snmp_trapV2 )
  			return false;
  
  		zeek::BifEvent::enqueue_snmp_trapV2(zeek_analyzer(),
  		                              zeek_analyzer()->Conn(),
  		                              ${pdu.header.is_orig},
  		                              build_hdr(${pdu.header}),
  		                              build_pdu(${pdu.pdu}));
  		return true;
  		%}
  
  	function proc_report(pdu: ReportPDU): bool
  		%{
  		if ( ! snmp_report )
  			return false;
  
  		zeek::BifEvent::enqueue_snmp_report(zeek_analyzer(),
  		                              zeek_analyzer()->Conn(),
  		                              ${pdu.header.is_orig},
  		                              build_hdr(${pdu.header}),
  		                              build_pdu(${pdu.pdu}));
  		return true;
  		%}
  
  	function proc_unknown_version_header(rec: UnknownVersionHeader): bool
  		%{
  		if ( ! snmp_unknown_header_version )
  			return false;
  
  		zeek::BifEvent::enqueue_snmp_unknown_header_version(zeek_analyzer(),
  		                                              zeek_analyzer()->Conn(),
  		                                              ${rec.header.is_orig},
  		                                              ${rec.header.version});
  		return true;
  		%}
  
  	function proc_unknown_pdu(rec: UnknownPDU): bool
  		%{
  		if ( ! snmp_unknown_pdu )
  			return false;
  
  		zeek::BifEvent::enqueue_snmp_unknown_pdu(zeek_analyzer(),
  		                                   zeek_analyzer()->Conn(),
  		                                   ${rec.header.is_orig},
  		                                   build_hdr(${rec.header}),
  		                                   ${rec.tag});
  		return true;
  		%}
  
  	function proc_unknown_scoped_pdu(rec: UnknownScopedPDU): bool
  		%{
  		if ( ! snmp_unknown_scoped_pdu )
  			return false;
  
  		zeek::BifEvent::enqueue_snmp_unknown_scoped_pdu(zeek_analyzer(),
  		                                          zeek_analyzer()->Conn(),
  		                                          ${rec.header.is_orig},
  		                                          build_hdr(${rec.header}),
  		                                          ${rec.tag});
  		return true;
  		%}
  
  	function proc_encrypted_pdu(rec: EncryptedPDU): bool
  		%{
  		if ( ! snmp_encrypted_pdu )
  			return false;
  
  		zeek::BifEvent::enqueue_snmp_encrypted_pdu(zeek_analyzer(),
  		                                     zeek_analyzer()->Conn(),
  		                                     ${rec.header.is_orig},
  		                                     build_hdr(${rec.header}));
  		return true;
  		%}
  
  	function proc_header(rec: Header): bool
  		%{
  		if ( ! ${rec.is_orig} )
  			zeek_analyzer()->AnalyzerConfirmation();
  
  		if ( rec->unknown() )
  			return false;
  
  		return true;
  		%}
  
  	function proc_v3_header_data(rec: v3HeaderData): bool
  		%{
  		if ( rec->flags()->encoding()->content().length() == 1 )
  			return true;
  
  		zeek_analyzer()->AnalyzerViolation("Invalid v3 HeaderData msgFlags");
  		return false;
  		%}
  
  	function check_tag(rec: ASN1EncodingMeta, expect: uint8): bool
  		%{
  		if ( rec->tag() == expect )
  			return true;
  
  		// Unwind now to stop parsing because it's definitely the
  		// wrong protocol and parsing further could be expensive.
  		// Upper layer of analyzer will catch and call AnalyzerViolation().
  		throw binpac::Exception(zeek::util::fmt("Got ASN.1 tag %d, expect %d",
  		                        rec->tag(), expect));
  		return false;
  		%}
  
  	function check_int_width(rec: ASN1Integer): bool
  		%{
  		int len = rec->encoding()->content().length();
  
  		if ( len <= 9 )
  			// All integers use two's complement form, so an unsigned 64-bit
  			// integer value can require 9 octets to encode if the highest
  			// order bit is set.
  			return true;
  
  		throw binpac::Exception(zeek::util::fmt("ASN.1 integer width overflow: %d", len));
  		return false;
  		%}
  
  	function check_int(rec: ASN1Integer): bool
  		%{
  		return check_tag(rec->encoding()->meta(), ASN1_INTEGER_TAG) &&
  		       check_int_width(rec);
  		%}
  };
  
  refine typeattr GetRequestPDU += &let {
  	proc: bool = $context.connection.proc_get_request(this);
  };
  refine typeattr GetNextRequestPDU += &let {
  	proc: bool = $context.connection.proc_get_next_request(this);
  };
  refine typeattr ResponsePDU += &let {
  	proc: bool = $context.connection.proc_response(this);
  };
  refine typeattr SetRequestPDU += &let {
  	proc: bool = $context.connection.proc_set_request(this);
  };
  refine typeattr TrapPDU += &let {
  	proc: bool = $context.connection.proc_trap(this);
  };
  refine typeattr GetBulkRequestPDU += &let {
  	proc: bool = $context.connection.proc_get_bulk_request(this);
  };
  refine typeattr InformRequestPDU += &let {
  	proc: bool = $context.connection.proc_inform_request(this);
  };
  refine typeattr v2TrapPDU += &let {
  	proc: bool = $context.connection.proc_v2_trap(this);
  };
  refine typeattr ReportPDU += &let {
  	proc: bool = $context.connection.proc_report(this);
  };
  
  refine typeattr UnknownVersionHeader += &let {
  	proc: bool = $context.connection.proc_unknown_version_header(this);
  };
  refine typeattr UnknownPDU += &let {
  	proc: bool = $context.connection.proc_unknown_pdu(this);
  };
  refine typeattr UnknownScopedPDU += &let {
  	proc: bool = $context.connection.proc_unknown_scoped_pdu(this);
  };
  refine typeattr EncryptedPDU += &let {
  	proc: bool = $context.connection.proc_encrypted_pdu(this);
  };
  
  refine typeattr Header += &let {
  	proc: bool = $context.connection.proc_header(this);
  };
  
  refine typeattr v3HeaderData += &let {
  	proc: bool = $context.connection.proc_v3_header_data(this);
  };
  
  refine typeattr NetworkAddress += &let {
  	valid: bool = $context.connection.check_tag(encoding.meta,
  	                                            APP_IPADDRESS_TAG);
  };
  refine typeattr TimeTicks += &let {
  	valid: bool = $context.connection.check_tag(asn1_integer.meta,
  	                                            APP_TIMETICKS_TAG);
  };
  
  refine typeattr ASN1SequenceMeta += &let {
  	valid: bool = $context.connection.check_tag(encoding,
  	                                            ASN1_SEQUENCE_TAG);
  };
  refine typeattr ASN1Integer += &let {
  	valid: bool = $context.connection.check_int(this);
  };
  refine typeattr ASN1OctetString += &let {
  	valid: bool = $context.connection.check_tag(encoding.meta,
  	                                            ASN1_OCTET_STRING_TAG);
  };
  refine typeattr ASN1ObjectIdentifier += &let {
  	valid: bool = $context.connection.check_tag(encoding.meta,
  	                                            ASN1_OBJECT_IDENTIFIER_TAG);
  };
  