/*-*-linux-c-*-*/

/*
 *  gnewtellium - Newtella for Unix
 *  Copyright (C) 2001 Elias Athanasopoulos
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */


/* These includes are needed in the specified
   order by the BSD family. It maybe better to
   add an #ifdef BSD or something here...
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <stdlib.h>
#include <string.h>

#include "newtella.h"

#include "guid.h"
#include "connection.h"
#include "gnutella.h"
#include "global.h"
#include "file.h"
#include "hosts.h"
#include "uploads.h"


gint gnutella_send_ping(struct newtella_connection *con, gint wake_up)
{
	struct gnutella_header *header = newtella_malloc(sizeof *header);
	gchar *data;

	guidget(header->guid);
	header->f      = GNUTELLA_INIT;
	if (wake_up)
		header->ttl = 1;
	else
		header->ttl = GNUTELLA_PING_TTL;
	header->hops   = 0;
	header->length = 0;

	data = newtella_malloc(GN_HEADER_SIZE);
	GNUTELLA_HDR_ATTACH(data, header)
	con_send_packet(con, data, GN_HEADER_SIZE, 0);

	newtella_free(header);
	newtella_free(data);
	return 1;
}

gint gnutella_send_pong(struct newtella_connection *con, gchar *guid, guint8 ttl)
{
	struct gnutella_header *header;
	struct gnutella_pong_header *gpong;
	guint32 pack_size = 0;
	gchar *data;

	header = newtella_malloc(sizeof *header);
	gpong  = newtella_malloc(sizeof *gpong);

	guidcpy(header->guid, guid);
	header->f      = GNUTELLA_INITRESPONSE;
	header->hops   = 0;
	header->ttl    = ttl;
	header->length = GN_PONG_SIZE;

	bzero(gpong->host_ip, sizeof(gpong->host_ip));
	memcpy(gpong->host_ip, gl_my_host_info->ip, sizeof(gpong->host_ip));
	gpong->host_port        = gl_my_host_info->port;
	gpong->file_count       = gl_my_host_info->file_count;
	gpong->files_total_size = gl_my_host_info->files_total_size;

	pack_size = GN_HEADER_SIZE + header->length;

	data = newtella_malloc(pack_size);
	GNUTELLA_HDR_ATTACH(data, header)

	data += GN_HEADER_SIZE;

	memcpy(data, &gpong->host_port, 2);
	memcpy(data+2, gpong->host_ip, 16);
	memcpy(data+18, &gpong->file_count, 4);
	memcpy(data+22, &gpong->files_total_size, 4);

	data -= GN_HEADER_SIZE;
        con_send_packet(con, data, pack_size, 1);

	newtella_free(header);
	newtella_free(gpong);
	newtella_free(data);

	return 1;
}

gint gnutella_send_cached_host(struct newtella_connection *con, struct host_info *host)
{
	struct gnutella_header *header;
	struct gnutella_pong_header *gpong;
	gchar *data;

	header = newtella_malloc(sizeof *header);
	gpong = newtella_malloc(sizeof *gpong);

	guidget(header->guid);
	header->f      = GNUTELLA_INITRESPONSE;
	header->hops   = 0;
	header->ttl    = 1;
	header->length = GN_PONG_SIZE;

	bzero(gpong->host_ip, sizeof(gpong->host_ip));
	memcpy(gpong->host_ip, host->ip, sizeof(gpong->host_ip));
	gpong->host_port        = host->port;
	gpong->file_count       = host->file_count;
	gpong->files_total_size = host->files_total_size;

	data = newtella_malloc(GN_HEADER_SIZE + header->length);
	GNUTELLA_HDR_ATTACH(data, header)

	data += GN_HEADER_SIZE;

	memcpy(data, &gpong->host_port, 2);
	memcpy(data+2, gpong->host_ip, 16);
	memcpy(data+18, &gpong->file_count, 4);
	memcpy(data+22, &gpong->files_total_size, 4);

	data -= GN_HEADER_SIZE;

	con_send_packet(con, data, GN_HEADER_SIZE + header->length, 0);

	newtella_free(header);
	newtella_free(gpong);
	newtella_free(data);

	return 1;
}

gint gnutella_send_query(struct squery *query, struct newtella_connection *con, gint broadcast)
{
	struct  gnutella_header *header;
	struct  gnutella_query_header *query_header;
	guint32 pack_size = 0;
	gchar  *data;

        query_header = newtella_malloc(sizeof *query_header);
	query_header->min_speed = 0;

	header = newtella_malloc(sizeof *header);
	guidcpy(header->guid, query->guid);
	header->f      = GNUTELLA_SEARCH;
	header->ttl    = GNUTELLA_SEARCH_TTL;
	header->hops   = 0;
	header->length = GN_QUERY_SIZE + strlen(query->keyword) + 1;

	pack_size = GN_HEADER_SIZE + header->length;

	data = newtella_malloc(pack_size);
	GNUTELLA_HDR_ATTACH(data, header)

	data += GN_HEADER_SIZE;
	memcpy(data, &query_header->name, GN_QUERY_SIZE);

	data += GN_QUERY_SIZE;
	strcpy(data, query->keyword);

	data -= GN_HEADER_SIZE + GN_QUERY_SIZE;

	if (broadcast){
		con_send_to_all(data, pack_size, 0);
	}
	else {
		con_send_packet(con, data, pack_size, 0);
	}


	newtella_free(header);
	newtella_free(query_header);
	newtella_free(data);
	return 1;
}

gint gnutella_send_query_response(struct newtella_connection *con, GList *found_hits, gchar *guid)
{
	GList *iter;
	struct gnutella_hits *ghits = newtella_malloc(sizeof *ghits);
	struct gnutella_header *header = newtella_malloc(sizeof *header);
	struct file_entry *fe;

	gchar *data, *p;
	gchar open_data_byte = 0;

	gint i = 0, str_offs = 0, packet_size = 0;

	ghits->n    = g_list_length(found_hits);
	ghits->port = gl_my_host_info->port;

	if (host_is_private(gl_my_host_info->ip, gl_my_host_info->port)){
		bzero(ghits->ip, sizeof(ghits->ip));
		memcpy(ghits->ip ,gl_options->local_ip, sizeof(ghits->ip));
	}
	else {
		bzero(ghits->ip, sizeof(ghits->ip));
		memcpy(ghits->ip ,gl_my_host_info->ip, sizeof(ghits->ip));
	}

	ghits->speed = gl_options->con_speed;

	guidcpy(header->guid, guid);
	header->f    = GNUTELLA_SEARCHRESPONSE;
	header->ttl  = GNUTELLA_SEARCH_TTL;
	header->hops = 0;

	/* find out the size of the packet
	 * we have to add:
	 * - gnutella header
	 * - hits header
	 * - (hit header) x number of hits
	 * - size of each returned file name + 2 for \0\0
	 * - extended QHD: 1 length byte + 2 status bytes + 4 bytes for the vendor code
	 * - our guid (last 16 bytes)
	 */
	packet_size = GN_HEADER_SIZE + GN_HITS_SIZE +
		ghits->n*GN_HIT_SIZE + GN_EXTENDED_QHD_SIZE + 16;

	iter = g_list_first(found_hits);
	while (iter) {
		fe = (struct file_entry*)iter->data;
		/* add size of the file name to packet size */
		packet_size += (strlen(fe->name) + 2);

		iter = g_list_next(iter);
	}

        /* now we know exactly how many bytes
	   are needed for the new packet */
	data = newtella_malloc(packet_size);
	/* copy standard header */
	GNUTELLA_HDR_ATTACH(data, header)

	/* copy standard gnutella hits header */
	GNUTELLA_HITS_ATTACH((data+GN_HEADER_SIZE), ghits)

	/* copy each hit */
	iter = g_list_first(found_hits);
	while (iter) {
		fe = (struct file_entry*)iter->data;
		p = data+str_offs+GN_HEADER_SIZE+GN_HITS_SIZE+GN_HIT_SIZE*i;
		memcpy(p, &fe->index, 4);
		memcpy(p+4, &fe->size, 4);
		memcpy(p+8, fe->name, strlen(fe->name));
		memcpy(p+8+strlen(fe->name), "\0\0", 2);

		i++;
		str_offs += (strlen(fe->name)+2);
		iter = g_list_next(iter);
	}

	/* attach the QHD data */
	p = data+packet_size - 16 - GN_EXTENDED_QHD_SIZE;

	i = upload_active_con();
	if (i == gl_options->max_uploads)
		/* busy */
		open_data_byte |= QHD_BUSY_BIT_MASK;
	if (host_is_private(gl_my_host_info->ip, gl_my_host_info->port))
		/* push */
		open_data_byte |= QHD_PUSH_BIT_MASK;

	/* vendor */
	memcpy(p, "NEW6", 4);
	i = QHD_OD_SIZE;
	memcpy(p+4, &i, 1);
	memcpy(p+5, &open_data_byte, 1);
	memcpy(p+6, &open_data_byte, 1);


	/* copy guid to the last 16 bytes */
	guidcpy(data+packet_size-16, gl_options->gl_guid);
	header->length = packet_size - GN_HEADER_SIZE;

	/* adjust header length */
	memcpy(data+19, &header->length, 4);

	con_send_packet(con, data, header->length+GN_HEADER_SIZE, 1);

	newtella_free(header);
	newtella_free(ghits);
	newtella_free(data);
	return 1;
}

gint gnutella_send_push(struct newtella_connection *dcon)
{
	struct gnutella_header *header = newtella_malloc(sizeof *header);
	struct gnutella_push   *gpush  = newtella_malloc(sizeof *gpush);
	char *data;

	guidget(header->guid);
	header->f      = GNUTELLA_PUSHREQUEST;
	header->ttl    = GNUTELLA_SEARCH_TTL;
	header->hops   = 0;
	header->length = GN_PUSH_SIZE;

	guidcpy(gpush->guid, dcon->fi->guid);
	gpush->index = dcon->fi->index;
	bzero(gpush->ip, sizeof(gpush->ip));
	memcpy(gpush->ip , gl_options->local_ip, sizeof(gpush->ip));
	gpush->port  = dcon->port;

	data = newtella_malloc(GN_HEADER_SIZE+GN_PUSH_SIZE);
	GNUTELLA_HDR_ATTACH(data, header)

	data += GN_HEADER_SIZE;

	memcpy(data, &gpush->guid, 16);
	memcpy(data+16, &gpush->index, 4);
	memcpy(data+20, gpush->ip, 16);
       	memcpy(data+36, &gpush->port, 2);

	data -= GN_HEADER_SIZE;

 	con_send_to_all(data, GN_HEADER_SIZE+GN_PUSH_SIZE, 0);

	newtella_free(header);
	newtella_free(gpush);
	newtella_free(data);
	return 1;
}

/* various help functions */

gchar *translate_vendor_id(gchar *vid)
{
	if (!strncmp(vid, "GNEW", 4))
		return "Gnewtellium";
	if (!strncmp(vid, "NEW6", 4))
		return "Gnewtellium-IPv6";
	if (!strncmp(vid, "BEAR", 4))
		return "BearShare";
	if (!strncmp(vid, "LIME", 4))
		return "LimeWire";
	if (!strncmp(vid, "TOAD", 4))
		return "ToadNode";
	if (!strncmp(vid, "GNOT", 4))
		return "Gnotella";
	if (!strncmp(vid, "MACT", 4))
		return "Mactella";
	if (!strncmp(vid, "GNUC", 4))
		return "Gnucleus";
	if (!strncmp(vid, "GNUT", 4))
		return "Gnut";
	if (!strncmp(vid, "GTKG", 4))
		return "Gtk-Gnutella";
	if (!strncmp(vid, "NAPS", 4))
		return "NapShare";
	if (!strncmp(vid, "OCFG", 4))
		return "OpenCola";
	if (!strncmp(vid, "HSLG", 4))
		return "Hagelslag";
	if (!strncmp(vid, "CULT", 4))
		return "Cultiv8r";
	if (!strncmp(vid, "PHEX", 4))
		return "Phex";
	if (!strncmp(vid, "XOLO", 4))
		return "Xolox";
	if (!strncmp(vid, "MNAP", 4))
		return "MyNapster";
	if (!strncmp(vid, "SNUT", 4))
		return "SwapNut";
	if (!strncmp(vid, "QTEL", 4))
		return "QTella";
	else
//		return vid;
		return "Unknown";

}

gint rate_result_set(gint rs, gchar *open_data_byte, gchar *vendor)
{
	struct file_found *found;
	GList *iter;
	gint busy;
	gint push;

	if (((open_data_byte[1] & 4) == 4) && ((open_data_byte[2] & 4) == 4))
		busy = 1;
	else
		busy = 0;

	if (((open_data_byte[1] & 1) == 1) && ((open_data_byte[2] & 1) == 1))
		push = 1;
	else
		push = 0;

	iter = g_list_nth(gl_results, rs);
	while (iter) {
		found = iter->data;

		found->busy = busy;
		if (!found->push && push)
			found->push = push;

		memcpy(found->servent, vendor, 4);

		iter = g_list_next(iter);
	}

	return 1;
}


/* debug */
void gnutella_hdr_dump(struct gnutella_header *hdr)
{
	guiddump(hdr->guid);
	g_print("f: %d ttl: %d hops %d len: %d\n",
		hdr->f, hdr->ttl, hdr->hops, hdr->length);
}
