# $Id:$
#
# This code contributed by Nadi Sarrar.

connection BitTorrent_Conn(bro_analyzer: BroAnalyzer) {
	upflow = BitTorrent_Flow(true);
	downflow = BitTorrent_Flow(false);
};

flow BitTorrent_Flow(is_orig: bool) {
	flowunit = BitTorrent_PDU withcontext (connection, this);

	%member{
		bool handshake_ok;
		uint64 _next_message_offset;
	%}

	%init{
		handshake_ok = false;
		_next_message_offset = 0;
	%}

	function next_message_offset(): uint64
		%{
		return &_next_message_offset;
		%}

	function increment_next_message_offset(go: bool, len: uint32): bool
		%{
		if ( go )
			_next_message_offset += len;
		return true;
		%}

	function is_handshake_delivered(): bool
		%{
		return handshake_ok;
		%}

	function validate_handshake(pstrlen: uint8, pstr: const_bytestring): bool
		%{
		if ( pstrlen != 19 ||
		     memcmp("BitTorrent protocol", pstr.begin(), 19) )
			{
			connection()->bro_analyzer()->Weird(fmt("BitTorrent: invalid handshake (pstrlen: %hhu, pstr: %.*s)", pstrlen, 19, pstr.begin()));
			throw Exception("invalid handshake");
			}

		return true;
		%}

	function validate_message_length(len: uint32): bool
		%{
		if ( len > MSGLEN_LIMIT )
			throw Exception(fmt("message length prefix exceeds limit: %u > %u",
					len, MSGLEN_LIMIT));
		return true;
		%}

	function deliver_handshake(reserved: const_bytestring,
					info_hash: const_bytestring,
					peer_id: const_bytestring): bool
		%{
		handshake_ok = true;
		if ( ::bittorrent_peer_handshake )
			{
			bro_event_bittorrent_peer_handshake(
				connection()->bro_analyzer(),
				connection()->bro_analyzer()->Conn(),
				is_orig(),
				bytestring_to_val(reserved),
				bytestring_to_val(info_hash),
				bytestring_to_val(peer_id));
			}

		connection()->bro_analyzer()->ProtocolConfirmation();

		return true;
		%}

	function deliver_keep_alive(): bool
		%{
		if ( ::bittorrent_peer_keep_alive )
			{
			bro_event_bittorrent_peer_keep_alive(
				connection()->bro_analyzer(),
				connection()->bro_analyzer()->Conn(),
				is_orig());
			}

		return true;
		%}

	function deliver_choke(): bool
		%{
		if ( ::bittorrent_peer_choke )
			{
			bro_event_bittorrent_peer_choke(
				connection()->bro_analyzer(),
				connection()->bro_analyzer()->Conn(),
				is_orig());
			}

		return true;
		%}

	function deliver_unchoke(): bool
		%{
		if ( ::bittorrent_peer_unchoke )
			{
			bro_event_bittorrent_peer_unchoke(
				connection()->bro_analyzer(),
				connection()->bro_analyzer()->Conn(),
				is_orig());
			}

		return true;
		%}

	function deliver_interested(): bool
		%{
		if ( ::bittorrent_peer_interested )
			{
			bro_event_bittorrent_peer_interested(
				connection()->bro_analyzer(),
				connection()->bro_analyzer()->Conn(),
				is_orig());
			}

		return true;
		%}

	function deliver_not_interested(): bool
		%{
		if ( ::bittorrent_peer_not_interested )
			{
			bro_event_bittorrent_peer_not_interested(
				connection()->bro_analyzer(),
				connection()->bro_analyzer()->Conn(),
				is_orig());
			}

		return true;
		%}

	function deliver_have(piece_index: uint32): bool
		%{
		if ( ::bittorrent_peer_have )
			{
			bro_event_bittorrent_peer_have(
				connection()->bro_analyzer(),
				connection()->bro_analyzer()->Conn(),
				is_orig(),
				piece_index);
			}

		return true;
		%}

	function deliver_bitfield(bitfield: const_bytestring): bool
		%{
		if ( ::bittorrent_peer_bitfield )
			{
			bro_event_bittorrent_peer_bitfield(
				connection()->bro_analyzer(),
				connection()->bro_analyzer()->Conn(),
				is_orig(),
				bytestring_to_val(bitfield));
			}

		return true;
		%}

	function deliver_request(index: uint32, begin: uint32,
					length: uint32): bool
		%{
		if ( ::bittorrent_peer_request )
			{
			bro_event_bittorrent_peer_request(
				connection()->bro_analyzer(),
				connection()->bro_analyzer()->Conn(),
				is_orig(),
				index, begin, length);
			}

		return true;
		%}

	function deliver_piece(index: uint32, begin: uint32,
				piece_length: uint32): bool
		%{
		if ( ::bittorrent_peer_piece )
			{
			bro_event_bittorrent_peer_piece(
				connection()->bro_analyzer(),
				connection()->bro_analyzer()->Conn(),
				is_orig(),
				index, begin, piece_length);
			}

		return true;
		%}

	function deliver_cancel(index: uint32, begin: uint32,
				length: uint32): bool
		%{
		if ( ::bittorrent_peer_cancel )
			{
			bro_event_bittorrent_peer_cancel(
				connection()->bro_analyzer(),
				connection()->bro_analyzer()->Conn(),
				is_orig(),
				index, begin, length);
			}

		return true;
		%}

	function deliver_port(listen_port: uint16): bool
		%{
		if ( ::bittorrent_peer_port )
			{
			bro_event_bittorrent_peer_port(
				connection()->bro_analyzer(),
				connection()->bro_analyzer()->Conn(),
				is_orig(),
				new PortVal(listen_port, TRANSPORT_TCP));
			}

		return true;
		%}

	function deliver_unknown(id: uint8, data: const_bytestring): bool
		%{
		if ( ::bittorrent_peer_unknown )
			{
			bro_event_bittorrent_peer_unknown(
				connection()->bro_analyzer(),
				connection()->bro_analyzer()->Conn(),
				is_orig(),
				id,
				bytestring_to_val(data));
			}

		return true;
		%}
};
